from __future__ import annotations

import json
import os
import re
from typing import Any, Dict, List

import pandas as pd

from ..services.engine_client import EngineClient
from ..api.intelligence_client import IntelligenceClient
from ..repos.tradeshow_repo import TradeshowRepo
from ..services.metrics import metrics
from ..model_defs.c2a_models import C2ASuggestion


class TradeshowWorker:
    def __init__(self, db, storage=None):
        self.db = db
        self.repo = TradeshowRepo(db)
        self.engine = EngineClient()
        self.intel = IntelligenceClient()
        self.storage = storage  # optional: if provided, must implement read(key)

    async def run_job(self, job_id: str) -> None:
        # Parse import_id from job_id pattern ts_{import_id}_{ts}
        m = re.match(r"^ts_(?P<imp>[a-f0-9]{16})_", job_id)
        if not m:
            return
        import_id = m.group("imp")
        row = await self.repo.get_import(import_id)
        if not row:
            return

        account_id = str(row["account_id"])
        artifact_key = row["artifact_key"]

        # Fetch policy thresholds from Intelligence
        policy = await self.intel.get_policy(account_id=account_id, segment=None)
        suggest = float(policy.get("suggest", 0.70))

        # Load dataset from storage
        df = await self._load_dataframe(artifact_key)

        # Build config using Intelligence auto-configure
        cfg = await self.intel.auto_configure(
            df, df, goal=os.getenv("TRADESHOW_GOAL", "match")
        )
        mappings = cfg.get("field_mappings") or cfg.get("mappings") or []

        # Chunking for scale
        try:
            chunk_rows = int(os.getenv("TRADESHOW_CHUNK_ROWS", "50000") or 50000)
        except Exception:
            chunk_rows = 50000

        total_rows = len(df)
        matched_count = 0
        last_result = None

        async def _emit_suggestions(matches_df):
            suggestions: List[C2ASuggestion] = []
            for idx, r in matches_df.iterrows():
                src_id = r.get("source_id") or r.get("src_id") or str(idx)
                fsd = (
                    r.get("field_score_details")
                    or r.get("feature_scores")
                    or r.get("field_scores")
                    or {}
                )
                reasons = self._top_reasons(fsd)
                s = C2ASuggestion(
                    policy_id=row.get("config_version") or "v0",
                    contact_id=str(src_id),
                    account_id=str(account_id),
                    job_id=job_id,
                    score=float(r.get("score") or r.get("match_score") or 0.0),
                    reasons=reasons,
                    field_score_details=fsd if isinstance(fsd, (dict, list)) else None,
                    explanation="tradeshow",
                )
                suggestions.append(s)
                if len(suggestions) >= 500:
                    self.db.add_all(suggestions)
                    await self.db.commit()
                    suggestions = []
            if suggestions:
                self.db.add_all(suggestions)
                await self.db.commit()

        if total_rows <= chunk_rows:
            last_result = await self.engine.match_records(
                df,
                df,
                mappings=mappings,
                threshold=suggest,
                job_id=job_id,
            )
            matched_count = int(len(last_result.matches))
            await _emit_suggestions(last_result.matches)
        else:
            processed = 0
            for start in range(0, total_rows, chunk_rows):
                end = min(start + chunk_rows, total_rows)
                chunk_df = df.iloc[start:end]
                res = await self.engine.match_records(
                    chunk_df,
                    df,
                    mappings=mappings,
                    threshold=suggest,
                    job_id=job_id,
                )
                last_result = res
                matched_count += int(len(res.matches))
                await _emit_suggestions(res.matches)
                processed = end
                pct = int(processed * 100 / max(1, total_rows))
                try:
                    await self.repo.save_report(
                        job_id=job_id,
                        account_id=account_id,
                        matched_count=matched_count,
                        new_count=max(0, processed - matched_count),
                        undo_token="n/a",
                        summary={"status": "running", "pct_complete": pct},
                    )
                except Exception:
                    pass

        new_count = max(0, total_rows - matched_count)

        # Final report save
        summary = {
            "engine_version": getattr(last_result, "engine_version", ""),
            "processing_ms": getattr(last_result, "processing_time_ms", 0),
            "config_hash": getattr(last_result, "config_hash", ""),
            "field_score_details": "histogram_omitted",
            "status": "completed",
            "pct_complete": 100,
        }
        await self.repo.save_report(
            job_id=job_id,
            account_id=account_id,
            matched_count=matched_count,
            new_count=new_count,
            undo_token="n/a",
            summary=summary,
        )

        try:
            await metrics.increment("job_completed")
            await metrics.increment("suggestions_emitted", by=matched_count + new_count)
        except Exception:
            pass

    async def _load_dataframe(self, key: str) -> pd.DataFrame:
        # Local storage: LocalStorageBackend used by TradeshowService has read()
        if self.storage and hasattr(self.storage, "read"):
            content = await self.storage.read(key)
            from io import BytesIO

            # Detect file type
            lower = (key or "").lower()
            if lower.endswith((".xlsx", ".xls")):
                return pd.read_excel(BytesIO(content))
            return pd.read_csv(BytesIO(content))
        # Fallback: try to use core io loader if available
        try:
            from fmatch.core.io import FileLoader

            loader = FileLoader()
            lazy = loader.scan_csv(key)
            return lazy.collect().to_pandas()
        except Exception:
            # Last resort: attempt local path
            import os

            if os.path.exists(key):
                if key.lower().endswith((".xlsx", ".xls")):
                    return pd.read_excel(key)
                return pd.read_csv(key)
            raise

    def _top_reasons(self, field_score_details: Any) -> Dict[str, Any]:
        # Summarize top contributions for explainability
        try:
            if isinstance(field_score_details, str):
                data = json.loads(field_score_details)
            else:
                data = field_score_details
            if isinstance(data, list) and data:
                data = data[:3]
            return {"top": data}
        except Exception:
            return {}
