from __future__ import annotations

from pathlib import Path
from typing import Any

from app.database import db_session
from app.ml.lstm_predictor import predict_lstm
from app.models import DataFile, DataPackage, DataPackageFile
from app.models.eval_management import EvalRecord
from app.models.train_management import SavedModel
from app.services.data_management_service import DataManagementService


class EvalService:
    def __init__(self) -> None:
        self._dm = DataManagementService()
        self._base_dir = Path(__file__).resolve().parents[2]

    def _resolve_model_path(self, file_path: str) -> Path:
        p = Path(file_path)
        if p.is_absolute():
            return p
        return self._base_dir / p

    # ── dropdown data ─────────────────────────────────────────────────────────

    def list_packages(self) -> list[dict[str, Any]]:
        with db_session() as session:
            rows = session.query(DataPackage).order_by(DataPackage.created_at.desc()).all()
            return [{'id': p.id, 'name': p.name, 'data_count': p.data_count} for p in rows]

    def list_saved_models(self) -> list[dict[str, Any]]:
        with db_session() as session:
            rows = session.query(SavedModel).order_by(SavedModel.created_at.desc()).all()
            return [{'id': m.id, 'model_name': m.model_name, 'package_name': m.package_name} for m in rows]

    # ── evaluate ──────────────────────────────────────────────────────────────

    def evaluate(self, model_id: int, package_id: int) -> dict[str, Any]:
        with db_session() as session:
            model = session.query(SavedModel).filter(SavedModel.id == model_id).first()
            if not model:
                raise ValueError('模型不存在')

            pkg = session.query(DataPackage).filter(DataPackage.id == package_id).first()
            if not pkg:
                raise ValueError('数据包不存在')

            model_path = self._resolve_model_path(model.file_path)
            model_name = model.model_name
            package_name = pkg.name

        if not model_path.exists():
            raise ValueError('模型文件不存在，请重新训练并保存模型')

        records = self._load_package_records(package_id)
        if not records:
            raise ValueError('数据包中没有有效数据')

        result = predict_lstm(records=records, model_path=model_path)

        with db_session() as session:
            record = EvalRecord(
                model_id=model_id,
                model_name=model_name,
                package_id=package_id,
                package_name=package_name,
                total_count=result['total_count'],
                mae=result['mae'],
                rmse=result['rmse'],
                chart_data=result['chart_data'],
            )
            session.add(record)
            session.commit()
            session.refresh(record)
            return self._record_to_dict(record)

    # ── list / get ────────────────────────────────────────────────────────────

    def list_records(self) -> list[dict[str, Any]]:
        with db_session() as session:
            rows = (
                session.query(EvalRecord)
                .order_by(EvalRecord.created_at.desc())
                .all()
            )
            return [self._record_to_dict(r, include_chart=False) for r in rows]

    def get_record(self, record_id: int) -> dict[str, Any]:
        with db_session() as session:
            row = session.query(EvalRecord).filter(EvalRecord.id == record_id).first()
            if not row:
                raise ValueError('评估记录不存在')
            return self._record_to_dict(row, include_chart=True)

    def delete_record(self, record_id: int) -> None:
        with db_session() as session:
            row = session.query(EvalRecord).filter(EvalRecord.id == record_id).first()
            if not row:
                raise ValueError('评估记录不存在')
            session.delete(row)
            session.commit()

    # ── helpers ───────────────────────────────────────────────────────────────

    def _load_package_records(self, package_id: int) -> list[dict[str, Any]]:
        with db_session() as session:
            pkg = session.query(DataPackage).filter(DataPackage.id == package_id).first()
            clean_rules = pkg.clean_rules if pkg else None
            pf_rows = (
                session.query(DataPackageFile)
                .filter(DataPackageFile.package_id == package_id)
                .order_by(DataPackageFile.sort_order.asc())
                .all()
            )
            file_ids = [pf.file_id for pf in pf_rows]
            if not file_ids:
                return []
            files = session.query(DataFile).filter(DataFile.id.in_(file_ids)).all()
            file_map = {f.id: f for f in files}

        all_records: list[dict[str, Any]] = []
        for fid in file_ids:
            if fid not in file_map:
                continue
            fmeta = file_map[fid]
            path = self._dm._resolve_local_file_path(fmeta.file_path, fmeta.stored_name)
            recs, _ = self._dm._read_records(path, limit=None)
            all_records.extend(recs)

        if clean_rules and clean_rules.get('enabled'):
            all_records = self._apply_clean_rules(all_records, clean_rules)

        return all_records

    @staticmethod
    def _apply_clean_rules(records: list[dict[str, Any]], clean_rules: dict) -> list[dict[str, Any]]:
        c_min = clean_rules.get('current_min')
        c_max = clean_rules.get('current_max')
        v_min = clean_rules.get('voltage_min')
        v_max = clean_rules.get('voltage_max')
        t_min = clean_rules.get('temperature_min')
        t_max = clean_rules.get('temperature_max')

        result: list[dict[str, Any]] = []
        for r in records:
            current = r.get('current')
            voltage = r.get('voltage')
            temp = r.get('actual_temperature')

            if current is not None:
                if c_min is not None and current < c_min:
                    continue
                if c_max is not None and current > c_max:
                    continue
            if voltage is not None:
                if v_min is not None and voltage < v_min:
                    continue
                if v_max is not None and voltage > v_max:
                    continue
            if temp is not None:
                if t_min is not None and temp < t_min:
                    continue
                if t_max is not None and temp > t_max:
                    continue

            result.append(r)
        return result

    @staticmethod
    def _record_to_dict(row: EvalRecord, include_chart: bool = True) -> dict[str, Any]:
        d: dict[str, Any] = {
            'id': row.id,
            'model_id': row.model_id,
            'model_name': row.model_name,
            'package_id': row.package_id,
            'package_name': row.package_name,
            'total_count': row.total_count,
            'mae': row.mae,
            'rmse': row.rmse,
            'created_at': row.created_at.strftime('%Y-%m-%d %H:%M:%S') if row.created_at else '',
        }
        if include_chart:
            d['chart_data'] = row.chart_data or []
        return d
