"""
Unified MCP tools for Report Parity & Progressive Validation Engine.

4 action-dispatched tools, 12 actions total:
- report_parity(action, ...) — 4 actions: compile_spec, run, status, certificate
- parity_test(action, ...) — 3 actions: execute, classify, fix
- parity_report(action, ...) — 3 actions: analyze, suggest, compare
- parity_manage(action, ...) — 2 actions: list_runs, reset_gate
"""
from __future__ import annotations

import json
import logging
from typing import Any, Dict, List, Optional

logger = logging.getLogger(__name__)

# ============================================================================
# Lazy singletons
# ============================================================================

_compiler = None
_runner = None
_advisor = None
_certificate_gen = None
_fix_bridge = None


def _ensure_compiler():
    global _compiler
    if _compiler is None:
        from .report_spec_compiler import ReportSpecCompiler
        _compiler = ReportSpecCompiler()
    return _compiler


def _ensure_runner():
    global _runner
    if _runner is None:
        from .progressive_runner import ProgressiveRunner
        _runner = ProgressiveRunner()
    return _runner


def _ensure_advisor():
    global _advisor
    if _advisor is None:
        from .improvement_advisor import ImprovementAdvisor
        _advisor = ImprovementAdvisor()
    return _advisor


def _ensure_certificate_gen():
    global _certificate_gen
    if _certificate_gen is None:
        from .parity_certificate import ParityCertificate
        _certificate_gen = ParityCertificate()
    return _certificate_gen


def _ensure_fix_bridge():
    global _fix_bridge
    if _fix_bridge is None:
        from .fix_loop_bridge import FixLoopBridge
        _fix_bridge = FixLoopBridge()
    return _fix_bridge


# ============================================================================
# report_parity handlers
# ============================================================================

def _rp_compile_spec(settings, **kwargs) -> Dict[str, Any]:
    report_json = kwargs.get("report_json", "")
    expected_json = kwargs.get("expected_values_json", "")

    if not report_json:
        return {"error": "report_json is required"}

    try:
        report = json.loads(report_json) if isinstance(report_json, str) else report_json
        expected = json.loads(expected_json) if isinstance(expected_json, str) and expected_json else {}
    except json.JSONDecodeError as e:
        return {"error": f"Invalid JSON: {e}"}

    compiler = _ensure_compiler()

    if "kpis" in report:
        spec = compiler.compile_from_blce(report, expected)
    else:
        spec = compiler.compile(
            report_name=report.get("report_name", ""),
            source_system=report.get("source_system", ""),
            line_items=report.get("line_items", []),
            grain_columns=report.get("grain_columns"),
            temporal_column=report.get("temporal_column", ""),
            temporal_grain=report.get("temporal_grain", "monthly"),
            global_filters=report.get("filters"),
            metadata=report.get("metadata"),
        )

    return {"status": "success", "spec": spec.model_dump()}


def _rp_run(settings, **kwargs) -> Dict[str, Any]:
    spec_json = kwargs.get("spec_json", "")
    actual_values_json = kwargs.get("actual_values_json", "")

    if not spec_json:
        return {"error": "spec_json is required"}

    try:
        from .contracts import ReportSpec
        spec_data = json.loads(spec_json) if isinstance(spec_json, str) else spec_json
        spec = ReportSpec(**spec_data)
        actuals = json.loads(actual_values_json) if isinstance(actual_values_json, str) and actual_values_json else None
    except Exception as e:
        return {"error": f"Invalid input: {e}"}

    runner = _ensure_runner()
    state = runner.run_progressive(spec, actuals)
    return {
        "status": "success",
        "state": state.model_dump(),
        "overall_status": state.overall_status,
    }


def _rp_status(settings, **kwargs) -> Dict[str, Any]:
    state_id = kwargs.get("state_id", "")
    if not state_id:
        return {"error": "state_id is required"}
    runner = _ensure_runner()
    return runner.get_status(state_id)


def _rp_certificate(settings, **kwargs) -> Dict[str, Any]:
    state_id = kwargs.get("state_id", "")
    state_json = kwargs.get("state_json", "")

    if not state_id and not state_json:
        return {"error": "state_id or state_json is required"}

    gen = _ensure_certificate_gen()

    if state_json:
        try:
            from .contracts import ProgressiveValidationState
            data = json.loads(state_json) if isinstance(state_json, str) else state_json
            state = ProgressiveValidationState(**data)
        except Exception as e:
            return {"error": f"Invalid state JSON: {e}"}
    else:
        runner = _ensure_runner()
        status = runner.get_status(state_id)
        if "error" in status:
            return status
        return {"error": "state_json required for certificate generation"}

    cert = gen.generate(state)
    cert["markdown"] = gen.to_markdown(cert)
    return {"status": "success", "certificate": cert}


# ============================================================================
# parity_test handlers
# ============================================================================

def _pt_execute(settings, **kwargs) -> Dict[str, Any]:
    spec_json = kwargs.get("spec_json", "")
    line_item_id = kwargs.get("line_item_id", "")
    period = kwargs.get("period", "month")
    period_value = kwargs.get("period_value", "")
    actual_value = kwargs.get("actual_value")

    if not spec_json:
        return {"error": "spec_json is required"}

    try:
        from .contracts import ReportSpec, ValidationPeriod
        spec_data = json.loads(spec_json) if isinstance(spec_json, str) else spec_json
        spec = ReportSpec(**spec_data)
        vp = ValidationPeriod(period)
    except Exception as e:
        return {"error": f"Invalid input: {e}"}

    from .parity_test_executor import ParityTestExecutor
    executor = ParityTestExecutor()

    item = None
    for li in spec.line_items:
        if li.line_id == line_item_id or li.label == line_item_id:
            item = li
            break

    if not item and spec.line_items:
        item = spec.line_items[0]

    if not item:
        return {"error": "No line items found in spec"}

    actual = float(actual_value) if actual_value is not None else None
    result = executor.execute_test(spec, item, vp, period_value, actual)
    return {"status": "success", "result": result.model_dump()}


def _pt_classify(settings, **kwargs) -> Dict[str, Any]:
    result_json = kwargs.get("result_json", "")
    if not result_json:
        return {"error": "result_json is required"}

    try:
        from .contracts import ParityTestResult
        data = json.loads(result_json) if isinstance(result_json, str) else result_json
        result = ParityTestResult(**data)
    except Exception as e:
        return {"error": f"Invalid input: {e}"}

    from .discrepancy_classifier import DiscrepancyClassifier
    classifier = DiscrepancyClassifier()
    disc = classifier.classify(result)
    return {"status": "success", "discrepancy": disc.model_dump()}


def _pt_fix(settings, **kwargs) -> Dict[str, Any]:
    discrepancy_json = kwargs.get("discrepancy_json", "")
    spec_json = kwargs.get("spec_json", "")

    if not discrepancy_json or not spec_json:
        return {"error": "discrepancy_json and spec_json are required"}

    try:
        from .contracts import Discrepancy, ReportSpec
        disc = Discrepancy(**json.loads(discrepancy_json) if isinstance(discrepancy_json, str) else discrepancy_json)
        spec = ReportSpec(**json.loads(spec_json) if isinstance(spec_json, str) else spec_json)
    except Exception as e:
        return {"error": f"Invalid input: {e}"}

    bridge = _ensure_fix_bridge()
    return bridge.invoke_fix(disc, spec)


# ============================================================================
# parity_report handlers
# ============================================================================

def _pr_analyze(settings, **kwargs) -> Dict[str, Any]:
    spec_json = kwargs.get("spec_json", "")
    if not spec_json:
        return {"error": "spec_json is required"}

    try:
        from .contracts import ReportSpec
        spec = ReportSpec(**json.loads(spec_json) if isinstance(spec_json, str) else spec_json)
    except Exception as e:
        return {"error": f"Invalid input: {e}"}

    advisor = _ensure_advisor()
    suggestions = advisor.analyze(spec)
    return {
        "status": "success",
        "suggestions": suggestions,
        "total": len(suggestions),
        "by_severity": {
            "critical": sum(1 for s in suggestions if s["severity"] == "critical"),
            "warning": sum(1 for s in suggestions if s["severity"] == "warning"),
            "info": sum(1 for s in suggestions if s["severity"] == "info"),
        },
    }


def _pr_suggest(settings, **kwargs) -> Dict[str, Any]:
    return _pr_analyze(settings, **kwargs)


def _pr_compare(settings, **kwargs) -> Dict[str, Any]:
    spec_json = kwargs.get("spec_json", "")
    actual_values_json = kwargs.get("actual_values_json", "")

    if not spec_json:
        return {"error": "spec_json is required"}

    try:
        from .contracts import ReportSpec
        spec = ReportSpec(**json.loads(spec_json) if isinstance(spec_json, str) else spec_json)
        actuals = json.loads(actual_values_json) if isinstance(actual_values_json, str) and actual_values_json else {}
    except Exception as e:
        return {"error": f"Invalid input: {e}"}

    comparisons = []
    for item in spec.line_items:
        for period, expected in item.expected_values.items():
            actual = actuals.get(f"{item.line_id}:{period}", actuals.get(item.line_id, 0.0))
            diff = actual - expected
            diff_pct = diff / abs(expected) if expected != 0 else 0.0
            comparisons.append({
                "line_item": item.label,
                "period": period,
                "expected": expected,
                "actual": actual,
                "difference": round(diff, 6),
                "difference_pct": round(diff_pct, 6),
                "status": "pass" if abs(diff_pct) <= 0.01 else "fail",
            })

    return {
        "status": "success",
        "comparisons": comparisons,
        "total": len(comparisons),
        "passed": sum(1 for c in comparisons if c["status"] == "pass"),
        "failed": sum(1 for c in comparisons if c["status"] == "fail"),
    }


# ============================================================================
# parity_manage handlers
# ============================================================================

def _pm_list_runs(settings, **kwargs) -> Dict[str, Any]:
    runner = _ensure_runner()
    runs = runner.list_runs()
    return {"status": "success", "runs": runs, "total": len(runs)}


def _pm_reset_gate(settings, **kwargs) -> Dict[str, Any]:
    state_id = kwargs.get("state_id", "")
    gate_index = kwargs.get("gate_index")

    if not state_id:
        return {"error": "state_id is required"}
    if gate_index is None:
        return {"error": "gate_index is required"}

    runner = _ensure_runner()
    return runner.reset_gate(state_id, int(gate_index))


# ============================================================================
# Dispatch maps
# ============================================================================

_RP_ACTIONS = {
    "compile_spec": _rp_compile_spec,
    "run": _rp_run,
    "status": _rp_status,
    "certificate": _rp_certificate,
}

_PT_ACTIONS = {
    "execute": _pt_execute,
    "classify": _pt_classify,
    "fix": _pt_fix,
}

_PR_ACTIONS = {
    "analyze": _pr_analyze,
    "suggest": _pr_suggest,
    "compare": _pr_compare,
}

_PM_ACTIONS = {
    "list_runs": _pm_list_runs,
    "reset_gate": _pm_reset_gate,
}


# ============================================================================
# Unified dispatch functions
# ============================================================================

def dispatch_report_parity(settings, action: str, **kwargs) -> Dict[str, Any]:
    handler = _RP_ACTIONS.get(action)
    if not handler:
        return {"error": f"Unknown action: '{action}'", "valid_actions": sorted(_RP_ACTIONS.keys())}
    try:
        return handler(settings, **kwargs)
    except Exception as e:
        logger.error("report_parity(%s) failed: %s", action, e)
        return {"error": f"report_parity({action}) failed: {e}"}


def dispatch_parity_test(settings, action: str, **kwargs) -> Dict[str, Any]:
    handler = _PT_ACTIONS.get(action)
    if not handler:
        return {"error": f"Unknown action: '{action}'", "valid_actions": sorted(_PT_ACTIONS.keys())}
    try:
        return handler(settings, **kwargs)
    except Exception as e:
        logger.error("parity_test(%s) failed: %s", action, e)
        return {"error": f"parity_test({action}) failed: {e}"}


def dispatch_parity_report(settings, action: str, **kwargs) -> Dict[str, Any]:
    handler = _PR_ACTIONS.get(action)
    if not handler:
        return {"error": f"Unknown action: '{action}'", "valid_actions": sorted(_PR_ACTIONS.keys())}
    try:
        return handler(settings, **kwargs)
    except Exception as e:
        logger.error("parity_report(%s) failed: %s", action, e)
        return {"error": f"parity_report({action}) failed: {e}"}


def dispatch_parity_manage(settings, action: str, **kwargs) -> Dict[str, Any]:
    handler = _PM_ACTIONS.get(action)
    if not handler:
        return {"error": f"Unknown action: '{action}'", "valid_actions": sorted(_PM_ACTIONS.keys())}
    try:
        return handler(settings, **kwargs)
    except Exception as e:
        logger.error("parity_manage(%s) failed: %s", action, e)
        return {"error": f"parity_manage({action}) failed: {e}"}
