"""Gremlin v2 formula fixer API."""

from __future__ import annotations

import json
import logging
from typing import Any, List, Optional
from uuid import uuid4

from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field

from ... import settings as saas_settings
from ...auth_security import require_account
from ..progress import get_redis
from ...services import formula_fixer_service as fixer_service
from g_gremlin import graph as gremlin_graph

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v2/gremlin/formula-fixer", tags=["gremlin"])


class SparseCell(BaseModel):
    a1: str
    formula: Optional[str] = None
    display_value: Optional[Any] = None
    sheet: str


class FormulaFixerScanRequest(BaseModel):
    spreadsheet_id: str
    sheet_name: Optional[str] = None
    scan_mode: str = Field("current_tab", description="current_tab|selected_tabs|all_tabs")
    selected_sheets: Optional[List[str]] = None
    sheet_titles: Optional[List[str]] = None
    cells: List[SparseCell]


class HealthModel(BaseModel):
    score: int
    grade: str


class FixProposal(BaseModel):
    cell: str
    expected_original: Optional[str] = None
    proposed: str


class IssueGroup(BaseModel):
    issue_id: Optional[str] = None
    type: str
    title: str = ""
    cells: List[str]
    sample_original: Optional[str] = None
    sample_fixed: Optional[str] = None
    fix_available: bool = False
    fix_confidence_band: str
    recommended_action: str
    diagnostic_actions: List[str] = Field(default_factory=list)
    fixes: List[FixProposal] = Field(default_factory=list)
    explanation: str = ""


class FormulaFixerScanResponse(BaseModel):
    health: HealthModel
    issue_groups: List[IssueGroup]
    truncated: bool = False
    truncation_message: Optional[str] = None


class FormulaFixerDiagnoseRequest(BaseModel):
    spreadsheet_id: str
    issue_id: str
    action: str = Field("dependency_path", description="dependency_path|diagnostic_tab|highlight_cells")


class FormulaFixerDiagnoseResponse(BaseModel):
    dependency_path: Optional[List[dict]] = None
    diagnostic_tab_data: Optional[dict] = None
    highlight_cells: Optional[List[str]] = None
    suspect_cells: Optional[List[dict]] = None


class FormulaFixerValidateItem(BaseModel):
    cell: str
    expected_original: Optional[str] = None
    proposed: str


class FormulaFixerValidateRequest(BaseModel):
    spreadsheet_id: str
    fixes: List[FormulaFixerValidateItem]


class FormulaFixerValidateResponse(BaseModel):
    valid: bool
    stale_cells: List[str] = Field(default_factory=list)


class FormulaFixerLogAppliedRequest(BaseModel):
    spreadsheet_id: str
    step_id: str
    fixes_applied: List[dict] = Field(default_factory=list)


class FormulaFixerLogAppliedResponse(BaseModel):
    logged: bool


def _ensure_enabled() -> None:
    if not saas_settings.GREMLIN_V2_ENABLED:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Gremlin v2 disabled")
    if not saas_settings.GREMLIN_V2_FORMULA_FIXER:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Formula fixer disabled")


@router.post("/scan", response_model=FormulaFixerScanResponse)
async def scan_formulas(
    request: FormulaFixerScanRequest,
    account_id: str = Depends(require_account),
):
    del account_id
    _ensure_enabled()

    raw_cells = [cell.model_dump() for cell in request.cells]
    cells, truncated, message = fixer_service.clamp_cells(raw_cells)

    existing_sheets = (
        request.sheet_titles
        or request.selected_sheets
        or sorted({cell.get("sheet") for cell in cells if cell.get("sheet")})
    )
    scan_id = str(uuid4())

    payload = fixer_service.build_scan_payload(cells, existing_sheets, scan_id)
    issue_groups = payload.get("issue_groups", [])

    redis = await get_redis()
    if redis:
        cache_key = fixer_service.SCAN_CACHE_KEY.format(scan_id=scan_id)
        await redis.setex(
            cache_key,
            fixer_service.SCAN_CACHE_TTL_SECONDS,
            fixer_service.build_scan_cache_payload(payload),
        )
        await redis.setex(
            fixer_service.LAST_SCAN_KEY.format(spreadsheet_id=request.spreadsheet_id),
            fixer_service.SCAN_CACHE_TTL_SECONDS,
            scan_id,
        )

    return FormulaFixerScanResponse(
        health=payload.get("health"),
        issue_groups=issue_groups,
        truncated=truncated,
        truncation_message=message,
    )


@router.post("/diagnose", response_model=FormulaFixerDiagnoseResponse)
async def diagnose_formula_issue(
    request: FormulaFixerDiagnoseRequest,
    account_id: str = Depends(require_account),
):
    del account_id
    _ensure_enabled()

    if ":" not in request.issue_id:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid issue_id")

    scan_id, issue_type = request.issue_id.split(":", 1)
    redis = await get_redis()
    if not redis:
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Diagnostics unavailable")

    raw = await redis.get(fixer_service.SCAN_CACHE_KEY.format(scan_id=scan_id))
    if not raw:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Scan data not found")

    cached = json.loads(raw)
    group = fixer_service.resolve_issue_group(cached, issue_type)
    if not group:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Issue group not found")

    cells_map = cached.get("cells") or {}
    cell_graph = cached.get("graph") or {}
    cells = group.get("cells") or []
    sample_cell = cells[0] if cells else None
    suspect_cells = (
        fixer_service.find_suspect_cells(sample_cell, cells_map, cell_graph)
        if sample_cell
        else []
    )

    if request.action == "highlight_cells":
        return FormulaFixerDiagnoseResponse(highlight_cells=cells)

    if request.action == "diagnostic_tab":
        trace = []
        if sample_cell:
            trace = gremlin_graph.trace_upstream(cell_graph, sample_cell, max_depth=10, cells_metadata=cells_map)
        rows = [[sample_cell or "", cells_map.get(sample_cell, {}).get("formula") or "", "root"]]
        for item in trace:
            rows.append([
                item.get("cell"),
                item.get("formula") or "",
                item.get("parent") or "",
            ])
        return FormulaFixerDiagnoseResponse(
            diagnostic_tab_data={
                "headers": ["Cell", "Formula", "Parent"],
                "rows": rows,
            },
            suspect_cells=suspect_cells or None,
        )

    trace = []
    if sample_cell:
        trace = gremlin_graph.trace_upstream(cell_graph, sample_cell, max_depth=10, cells_metadata=cells_map)
    dependency_path = []
    if sample_cell:
        dependency_path.append(
            {
                "cell": sample_cell,
                "formula": cells_map.get(sample_cell, {}).get("formula"),
                "references": cell_graph.get(sample_cell, {}).get("references", []),
            }
        )
    for item in trace:
        cell = item.get("cell")
        dependency_path.append(
            {
                "cell": cell,
                "formula": item.get("formula"),
                "references": cell_graph.get(cell, {}).get("references", []),
            }
        )

    return FormulaFixerDiagnoseResponse(
        dependency_path=dependency_path,
        suspect_cells=suspect_cells or None,
    )


@router.post("/validate", response_model=FormulaFixerValidateResponse)
async def validate_fixes(
    request: FormulaFixerValidateRequest,
    account_id: str = Depends(require_account),
):
    del account_id
    _ensure_enabled()

    redis = await get_redis()
    if not redis:
        return FormulaFixerValidateResponse(valid=True, stale_cells=[])

    scan_id_raw = await redis.get(
        fixer_service.LAST_SCAN_KEY.format(spreadsheet_id=request.spreadsheet_id)
    )
    if not scan_id_raw:
        return FormulaFixerValidateResponse(valid=True, stale_cells=[])

    scan_id = scan_id_raw.decode() if hasattr(scan_id_raw, "decode") else str(scan_id_raw)
    raw = await redis.get(fixer_service.SCAN_CACHE_KEY.format(scan_id=scan_id))
    if not raw:
        return FormulaFixerValidateResponse(valid=True, stale_cells=[])

    cached = json.loads(raw)
    cells_map = cached.get("cells") or {}

    stale: List[str] = []
    for fix in request.fixes:
        cell = fix.cell
        expected = fix.expected_original or ""
        if not expected:
            continue
        current = (cells_map.get(cell, {}) or {}).get("formula") or ""
        if current and expected != current:
            stale.append(cell)

    return FormulaFixerValidateResponse(valid=len(stale) == 0, stale_cells=stale)


@router.post("/log-applied", response_model=FormulaFixerLogAppliedResponse)
async def log_applied_fixes(
    request: FormulaFixerLogAppliedRequest,
    account_id: str = Depends(require_account),
):
    _ensure_enabled()
    logger.info(
        "Formula fixer applied: account=%s spreadsheet=%s step=%s fixes=%d",
        account_id,
        request.spreadsheet_id,
        request.step_id,
        len(request.fixes_applied or []),
    )
    return FormulaFixerLogAppliedResponse(logged=True)
