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 DataPackage
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, category_id: str = '', name: str = '') -> list[dict[str, Any]]:
        with db_session() as session:
            query = session.query(DataPackage)
            if category_id not in ('', 'all', None):
                try:
                    db_id = int(category_id)
                    query = query.filter(DataPackage.category_id == db_id)
                except (ValueError, TypeError):
                    pass
            if name:
                query = query.filter(DataPackage.name.like(f'%{name}%'))
            rows = query.order_by(DataPackage.created_at.desc()).all()
            return [{'id': p.id, 'name': p.name, 'data_count': p.data_count, 'category_id': p.category_id} 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'],
                mse=result['mse'],
                rmse=result['rmse'],
                mape=result['mape'],
                r2=result['r2'],
                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()
            if not pkg or not pkg.stored_name:
                return []
            stored_name = pkg.stored_name

        pkg_path = self._base_dir / 'uploads' / 'packages' / stored_name
        recs, _ = self._dm._read_records(pkg_path, limit=None)
        return recs

    @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,
            'mse': row.mse,
            'rmse': row.rmse,
            'mape': row.mape,
            'r2': row.r2,
            '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
