"""
Model explainer service: build workbook graphs and summaries from sparse cells.
"""

from __future__ import annotations

import logging
import re
from typing import Any, Dict, List, Optional, Tuple

from g_gremlin import graph as gremlin_graph
from .gremlin_ai import GremlinAI

logger = logging.getLogger(__name__)

_CELL_RE = re.compile(r"^([A-Za-z]+)(\d+)$")


def _parse_a1(a1: str) -> Tuple[Optional[str], Optional[int]]:
    match = _CELL_RE.match(a1 or "")
    if not match:
        return None, None
    return match.group(1).upper(), int(match.group(2))


def _column_index(col_letters: Optional[str]) -> Optional[int]:
    if not col_letters:
        return None
    value = 0
    for ch in col_letters.upper():
        value = value * 26 + (ord(ch) - ord("A") + 1)
    return value


def _label_for_cell(a1: str, headers: List[Any]) -> Optional[str]:
    col_letters, _ = _parse_a1(a1)
    col_idx = _column_index(col_letters)
    if col_idx is None:
        return None
    idx = col_idx - 1
    if 0 <= idx < len(headers):
        header = str(headers[idx] or "").strip()
        if header:
            return header
    return None


def _summarize_formula_patterns(formulas: List[str]) -> Dict[str, Any]:
    patterns: Dict[str, int] = {}
    for formula in formulas:
        details = gremlin_graph.classify_formula(formula or "")
        pattern = details.get("type") or "unknown"
        patterns[pattern] = patterns.get(pattern, 0) + 1
    return patterns


def _sheet_role(formula_count: int, driver_count: int, output_count: int) -> str:
    if formula_count == 0 and driver_count > 0:
        return "inputs"
    if formula_count == 0 and driver_count == 0:
        return "static"
    if output_count > 0 and formula_count > 0 and driver_count == 0:
        return "output"
    return "calc_model"


def build_workbook_maps(
    spreadsheet_id: str,
    cells: List[Dict[str, Any]],
    sheets_metadata: List[Dict[str, Any]],
    sheet_names: Optional[List[str]] = None,
) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]], Dict[str, Any]]:
    headers_by_sheet = {
        meta.get("name"): meta.get("headers") or [] for meta in sheets_metadata
    }
    metadata_by_sheet = {meta.get("name"): meta for meta in sheets_metadata}

    allowed_sheets = sheet_names or [meta.get("name") for meta in sheets_metadata]
    allowed_sheets = [s for s in allowed_sheets if s]

    filtered_cells = [
        cell for cell in cells if not allowed_sheets or cell.get("sheet") in allowed_sheets
    ]

    cells_map: Dict[str, Dict[str, Any]] = {}
    for cell in filtered_cells:
        a1 = str(cell.get("a1") or "").strip()
        sheet = str(cell.get("sheet") or "").strip()
        if not a1 or not sheet:
            continue
        full = f"{sheet}!{a1}"
        cells_map[full] = {
            "formula": cell.get("formula"),
            "value": cell.get("display_value"),
        }

    graphs = gremlin_graph.build_workbook_graph(cells_map)
    cell_graph = graphs.get("cell_graph", {})
    sheet_graph = graphs.get("sheet_graph", {})

    # Ensure referenced nodes exist for classification.
    for ref in cell_graph.keys():
        cells_map.setdefault(ref, {"formula": None, "value": None})

    classifications = gremlin_graph.classify_cells(cells_map, cell_graph)

    drivers = [cell for cell, kind in classifications.items() if kind == "driver"]
    outputs = [cell for cell, kind in classifications.items() if kind == "output"]

    top_drivers = sorted(
        drivers,
        key=lambda c: len(cell_graph.get(c, {}).get("dependents", [])),
        reverse=True,
    )[:10]

    top_outputs = outputs[:10]

    model_map: List[Dict[str, Any]] = []
    for sheet in allowed_sheets:
        meta = metadata_by_sheet.get(sheet, {})
        row_count = int(meta.get("row_count") or 0)
        formula_cells = [
            cell for cell, meta_cell in cells_map.items()
            if cell.startswith(f"{sheet}!")
            and isinstance(meta_cell.get("formula"), str)
            and str(meta_cell.get("formula") or "").startswith("=")
        ]
        sheet_drivers = [cell for cell in drivers if cell.startswith(f"{sheet}!")]
        sheet_outputs = [cell for cell in outputs if cell.startswith(f"{sheet}!")]

        role = _sheet_role(len(formula_cells), len(sheet_drivers), len(sheet_outputs))
        purpose = {
            "inputs": "Inputs and assumptions",
            "calc_model": "Model calculations",
            "output": "Outputs and results",
            "static": "Reference data",
        }.get(role, "Reference data")

        key_inputs = [cell.split("!", 1)[1] for cell in sheet_drivers[:5]]
        key_outputs = [cell.split("!", 1)[1] for cell in sheet_outputs[:5]]

        model_map.append(
            {
                "sheet_name": sheet,
                "role": role,
                "purpose": purpose,
                "row_count": row_count,
                "formula_count": len(formula_cells),
                "key_outputs": ", ".join(key_outputs),
                "key_inputs": ", ".join(key_inputs),
                "feeds_sheets": ", ".join(sheet_graph.get(sheet, {}).get("feeds", [])),
                "fed_by_sheets": ", ".join(sheet_graph.get(sheet, {}).get("fed_by", [])),
            }
        )

    inputs_map: List[Dict[str, Any]] = []
    for cell in top_drivers:
        sheet, a1 = cell.split("!", 1)
        headers = headers_by_sheet.get(sheet, [])
        label = _label_for_cell(a1, headers)
        value = cells_map.get(cell, {}).get("value")
        dependents = cell_graph.get(cell, {}).get("dependents", [])
        dependent_sheets = sorted({d.split("!", 1)[0] for d in dependents if "!" in d})
        sensitivity = "High" if len(dependents) >= 5 else "Medium"
        category = "Driver" if len(dependents) >= 3 else "Input"

        inputs_map.append(
            {
                "cell": cell,
                "label": label or a1,
                "current_value": value,
                "type": "input",
                "referenced_by_count": len(dependents),
                "referenced_by_sheets": ", ".join(dependent_sheets),
                "category": category,
                "sensitivity": sensitivity,
            }
        )

    formula_list = [
        meta.get("formula")
        for meta in cells_map.values()
        if isinstance(meta.get("formula"), str) and str(meta.get("formula")).startswith("=")
    ]

    context = {
        "workbook_structure": {
            sheet: {
                "rows": int((metadata_by_sheet.get(sheet, {}) or {}).get("row_count") or 0),
                "formula_count": len([
                    cell
                    for cell in cells_map
                    if cell.startswith(f"{sheet}!")
                    and isinstance(cells_map[cell].get("formula"), str)
                    and str(cells_map[cell].get("formula") or "").startswith("=")
                ]),
                "driver_count": len([cell for cell in drivers if cell.startswith(f"{sheet}!")]),
                "feeds": sheet_graph.get(sheet, {}).get("feeds", []),
                "fed_by": sheet_graph.get(sheet, {}).get("fed_by", []),
            }
            for sheet in allowed_sheets
        },
        "top_drivers": [
            {
                "cell": cell,
                "label": _label_for_cell(cell.split("!", 1)[1], headers_by_sheet.get(cell.split("!", 1)[0], [])),
                "referenced_by_count": len(cell_graph.get(cell, {}).get("dependents", [])),
            }
            for cell in top_drivers
        ],
        "top_outputs": [
            {
                "cell": cell,
                "label": _label_for_cell(cell.split("!", 1)[1], headers_by_sheet.get(cell.split("!", 1)[0], [])),
            }
            for cell in top_outputs
        ],
        "formula_patterns": _summarize_formula_patterns(formula_list),
    }

    return model_map, inputs_map, context


async def summarize_workbook(
    context: Dict[str, Any],
    tier: str,
    account_id: str,
    db,
) -> Optional[str]:
    gremlin_ai = GremlinAI(tier, mode="model")
    if not gremlin_ai.model:
        return None
    try:
        return await gremlin_ai.summarize_workbook(context, account_id, db)
    except Exception:
        logger.exception("Model explainer summary failed")
        return None
