"""
Push helpers for g-gremlin.
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import List, Optional, Tuple

from g_gremlin import google_client, local_state
from g_gremlin.a1 import cell_a1_from_offsets, col_index_to_letter, parse_a1_range
from g_gremlin.profile_io import load_profile_if_exists
from g_gremlin.models import (
    CellChange,
    DiffSummary,
    RowDiff,
    SheetDiff,
    SheetState,
)


def load_sheet_diff(path: Optional[Path] = None) -> SheetDiff:
    """
    Load the diff.json file into a SheetDiff model.
    """
    diff_path = path or (local_state.STATE_DIR / "diff.json")
    if not diff_path.exists():
        raise FileNotFoundError(
            f"No diff found at {diff_path}. Run g-gremlin diff first."
        )
    data = json.loads(diff_path.read_text(encoding="utf-8"))
    return _sheet_diff_from_dict(data)


def diff_has_changes(diff: SheetDiff) -> bool:
    return (
        diff.summary.changed_cells > 0
        or diff.summary.added_rows > 0
        or diff.summary.removed_rows > 0
    )


def check_concurrency(state: SheetState) -> Tuple[bool, Optional[str], Optional[str]]:
    """
    Compare stored concurrency token with the current remote metadata.

    Returns (unchanged, stored_marker, remote_marker).
    """
    metadata = google_client.get_sheet_metadata(state.spreadsheet_id)
    stored_marker = state.concurrency_token
    remote_marker = metadata.get("headRevisionId") or metadata.get("modifiedTime")

    unchanged = bool(stored_marker and remote_marker and stored_marker == remote_marker)
    return unchanged, stored_marker, remote_marker


def build_value_updates(
    state: SheetState, diff: SheetDiff
) -> Tuple[List[dict], List[str], List[str], List[Tuple[str, Optional[str], Optional[str]]]]:
    """
    Convert a SheetDiff into values.batchUpdate data entries.
    Returns (updates, warnings, skipped, preview).
    """
    parsed = parse_a1_range(state.range)
    warnings: List[str] = []
    skipped: List[str] = []
    preview: List[Tuple[str, Optional[str], Optional[str]]] = []

    profile = load_profile_if_exists()
    formula_cells = {cell.a1 for cell in profile.formula_cells} if profile else set()

    if diff.added_keys or diff.removed_keys:
        warnings.append(
            "Warning: added/removed rows detected "
            f"({len(diff.added_keys)} added, {len(diff.removed_keys)} removed). "
            "These are not handled in v0 push and will be ignored."
        )

    updates: List[dict] = []
    for row_diff in diff.row_diffs:
        for change in row_diff.changes:
            if change.old == change.new:
                continue
            sheet_row = parsed.start_row + 1 + change.row_index
            sheet_col = parsed.start_col + change.column_index
            if parsed.end_row is not None and sheet_row > parsed.end_row:
                skipped.append(
                    f"Skipped change for row '{row_diff.row_key}' beyond pulled rows "
                    f"(row {sheet_row} > {parsed.end_row})."
                )
                continue
            if parsed.end_col is not None and sheet_col > parsed.end_col:
                skipped.append(
                    f"Skipped change for row '{row_diff.row_key}' column "
                    f"'{change.column_name}' beyond pulled columns "
                    f"(col {col_index_to_letter(sheet_col)} > "
                    f"{col_index_to_letter(parsed.end_col)})."
                )
                continue
            try:
                a1 = cell_a1_from_offsets(
                    parsed, change.row_index, change.column_index
                )
            except ValueError as exc:
                skipped.append(
                    f"Skipped change for row '{row_diff.row_key}' "
                    f"column '{change.column_name}': {exc}"
                )
                continue
            if a1 in formula_cells:
                warnings.append(
                    f"Skipping update to {a1} because it contains a formula. "
                    "Run 'g-gremlin inspect' to review formulas."
                )
                continue
            value = "" if change.new is None else change.new
            updates.append({"range": a1, "values": [[value]]})
            preview.append((a1, change.old, change.new))

    return updates, warnings, skipped, preview


def send_updates(spreadsheet_id: str, updates: List[dict]) -> None:
    """
    Execute a values.batchUpdate request with the provided updates.
    """
    service = google_client.get_sheets_service()
    body = {"valueInputOption": "USER_ENTERED", "data": updates}
    request = service.spreadsheets().values().batchUpdate(
        spreadsheetId=spreadsheet_id, body=body
    )
    request.execute()


def _sheet_diff_from_dict(data: dict) -> SheetDiff:
    summary_data = data.get("summary", {})
    summary = DiffSummary(
        added_rows=int(summary_data.get("added_rows", 0)),
        removed_rows=int(summary_data.get("removed_rows", 0)),
        changed_rows=int(summary_data.get("changed_rows", 0)),
        changed_cells=int(summary_data.get("changed_cells", 0)),
    )

    row_diffs: List[RowDiff] = []
    for rd in data.get("row_diffs", []):
        changes: List[CellChange] = []
        for ch in rd.get("changes", []):
            changes.append(
                CellChange(
                    row_key=str(ch.get("row_key", "")),
                    row_index=int(ch.get("row_index", 0)),
                    column_name=str(ch.get("column_name", "")),
                    column_index=int(ch.get("column_index", 0)),
                    old=ch.get("old"),
                    new=ch.get("new"),
                )
            )
        row_diffs.append(
            RowDiff(
                row_key=str(rd.get("row_key", "")),
                row_index=int(rd.get("row_index", 0)),
                changes=changes,
            )
        )

    added_keys = [str(k) for k in data.get("added_keys", [])]
    removed_keys = [str(k) for k in data.get("removed_keys", [])]

    return SheetDiff(
        summary=summary,
        row_diffs=row_diffs,
        added_keys=added_keys,
        removed_keys=removed_keys,
    )
