import json
import os
from datetime import datetime, timezone

import joblib
import pandas as pd
from sklearn.ensemble import IsolationForest

from core.config import settings


class IsolationForestService:
    def __init__(self) -> None:
        self.model_path = os.path.join(settings.model_dir, settings.isolation_model_file)
        self.meta_path = os.path.join(settings.model_dir, settings.model_meta_file)
        self.feature_names = [
            "dispatch_weight",
            "received_weight",
            "weight_delta",
            "expected_duration_minutes",
            "actual_duration_minutes",
            "duration_delta",
            "deviation_distance_km",
            "unexpected_stop_minutes",
            "face_mismatch",
        ]
        self._model: IsolationForest | None = None

    def train(self, df: pd.DataFrame) -> None:
        os.makedirs(settings.model_dir, exist_ok=True)
        fit_df = df[self.feature_names].copy()
        model = IsolationForest(n_estimators=200, contamination=0.08, random_state=42)
        model.fit(fit_df)
        joblib.dump(model, self.model_path)
        self._model = model
        self._write_meta()

    def _write_meta(self) -> None:
        meta = {
            "version": "1.0.0",
            "trained_at": datetime.now(timezone.utc).isoformat(),
            "feature_set": self.feature_names,
        }
        with open(self.meta_path, "w", encoding="utf-8") as f:
            json.dump(meta, f, indent=2)

    def load(self) -> IsolationForest:
        if self._model is not None:
            return self._model
        self._model = joblib.load(self.model_path)
        return self._model

    def score(self, feature_row: dict[str, float]) -> tuple[float, bool]:
        model = self.load()
        x = pd.DataFrame([[feature_row[k] for k in self.feature_names]], columns=self.feature_names)
        raw = float(model.decision_function(x)[0])
        is_anomaly = bool(model.predict(x)[0] == -1)
        return raw, is_anomaly
