"""
Schema validation for Google Sheets - validate spreadsheet structure against YAML specs.

Allows defining expected cell types, value ranges, and formula dependencies.
"""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional

import yaml

from g_gremlin import google_client
from g_gremlin.graph import parse_formula_references


@dataclass
class ValidationIssue:
    """A single validation issue."""
    cell: str
    rule: str
    severity: str  # "error" or "warning"
    message: str
    expected: Optional[Any] = None
    actual: Optional[Any] = None


@dataclass
class ValidationResult:
    """Result of schema validation."""
    success: bool
    total_checks: int = 0
    passed: int = 0
    failed: int = 0
    warnings: int = 0
    issues: List[ValidationIssue] = field(default_factory=list)
    errors: List[str] = field(default_factory=list)


def load_schema(schema_path: Path) -> Optional[Dict[str, Any]]:
    """Load and parse a YAML schema file."""
    try:
        with open(schema_path, "r", encoding="utf-8") as f:
            return yaml.safe_load(f)
    except Exception:
        return None


def validate_spreadsheet(
    spreadsheet_id: str,
    schema: Dict[str, Any],
    strict: bool = False,
) -> ValidationResult:
    """
    Validate a spreadsheet against a schema.

    Args:
        spreadsheet_id: The spreadsheet ID
        schema: Parsed schema dictionary
        strict: If True, unknown cells in schema are errors; otherwise warnings

    Returns:
        ValidationResult with issues found
    """
    service = google_client.get_sheets_service()

    issues: List[ValidationIssue] = []
    total_checks = 0
    passed = 0
    failed = 0
    warnings = 0

    sheets_spec = schema.get("sheets", {})

    for sheet_name, sheet_rules in sheets_spec.items():
        cells_spec = sheet_rules.get("cells", {})

        if not cells_spec:
            continue

        # Fetch sheet data
        range_spec = f"'{sheet_name}'"
        try:
            values_resp = (
                service.spreadsheets()
                .values()
                .get(
                    spreadsheetId=spreadsheet_id,
                    range=range_spec,
                    valueRenderOption="UNFORMATTED_VALUE",
                )
                .execute()
            )
            formulas_resp = (
                service.spreadsheets()
                .values()
                .get(
                    spreadsheetId=spreadsheet_id,
                    range=range_spec,
                    valueRenderOption="FORMULA",
                )
                .execute()
            )
        except Exception as e:
            issues.append(ValidationIssue(
                cell=f"{sheet_name}!*",
                rule="sheet_exists",
                severity="error",
                message=f"Failed to fetch sheet '{sheet_name}': {e}",
            ))
            failed += 1
            continue

        values_grid = values_resp.get("values", [])
        formulas_grid = formulas_resp.get("values", [])

        # Build cell -> value/formula map
        cell_values: Dict[str, Any] = {}
        cell_formulas: Dict[str, str] = {}

        for r_idx, row in enumerate(values_grid):
            for c_idx, val in enumerate(row):
                col_letter = _col_index_to_letter(c_idx)
                cell_a1 = f"{col_letter}{r_idx + 1}"
                cell_values[cell_a1] = val

        for r_idx, row in enumerate(formulas_grid):
            for c_idx, formula in enumerate(row):
                col_letter = _col_index_to_letter(c_idx)
                cell_a1 = f"{col_letter}{r_idx + 1}"
                cell_formulas[cell_a1] = formula

        # Validate each cell rule
        for cell_a1, rules in cells_spec.items():
            full_cell = f"{sheet_name}!{cell_a1}"
            total_checks += 1

            value = cell_values.get(cell_a1)
            formula = cell_formulas.get(cell_a1, "")

            # Check type
            expected_type = rules.get("type")
            if expected_type:
                type_issue = _validate_type(full_cell, value, formula, expected_type)
                if type_issue:
                    issues.append(type_issue)
                    if type_issue.severity == "error":
                        failed += 1
                    else:
                        warnings += 1
                else:
                    passed += 1

            # Check min value
            min_val = rules.get("min")
            if min_val is not None and value is not None:
                total_checks += 1
                if isinstance(value, (int, float)) and value < min_val:
                    issues.append(ValidationIssue(
                        cell=full_cell,
                        rule="min_value",
                        severity="error",
                        message=f"Value {value} is below minimum {min_val}",
                        expected=f">= {min_val}",
                        actual=value,
                    ))
                    failed += 1
                else:
                    passed += 1

            # Check max value
            max_val = rules.get("max")
            if max_val is not None and value is not None:
                total_checks += 1
                if isinstance(value, (int, float)) and value > max_val:
                    issues.append(ValidationIssue(
                        cell=full_cell,
                        rule="max_value",
                        severity="error",
                        message=f"Value {value} exceeds maximum {max_val}",
                        expected=f"<= {max_val}",
                        actual=value,
                    ))
                    failed += 1
                else:
                    passed += 1

            # Check must_reference (for formulas)
            must_ref = rules.get("must_reference")
            if must_ref and formula:
                total_checks += 1
                refs = parse_formula_references(formula)
                refs_upper = [r.upper() for r in refs]
                missing = [r for r in must_ref if r.upper() not in refs_upper]
                if missing:
                    issues.append(ValidationIssue(
                        cell=full_cell,
                        rule="must_reference",
                        severity="error",
                        message=f"Formula missing required references: {', '.join(missing)}",
                        expected=must_ref,
                        actual=refs,
                    ))
                    failed += 1
                else:
                    passed += 1

            # Check must_not_reference
            must_not_ref = rules.get("must_not_reference")
            if must_not_ref and formula:
                total_checks += 1
                refs = parse_formula_references(formula)
                refs_upper = [r.upper() for r in refs]
                forbidden = [r for r in must_not_ref if r.upper() in refs_upper]
                if forbidden:
                    issues.append(ValidationIssue(
                        cell=full_cell,
                        rule="must_not_reference",
                        severity="error",
                        message=f"Formula references forbidden cells: {', '.join(forbidden)}",
                        expected=f"no refs to {must_not_ref}",
                        actual=refs,
                    ))
                    failed += 1
                else:
                    passed += 1

            # Check pattern (regex match)
            pattern = rules.get("pattern")
            if pattern and value is not None:
                total_checks += 1
                if not re.match(pattern, str(value)):
                    issues.append(ValidationIssue(
                        cell=full_cell,
                        rule="pattern",
                        severity="error",
                        message=f"Value '{value}' does not match pattern '{pattern}'",
                        expected=pattern,
                        actual=value,
                    ))
                    failed += 1
                else:
                    passed += 1

            # Check not_empty
            if rules.get("not_empty"):
                total_checks += 1
                if value is None or (isinstance(value, str) and not value.strip()):
                    issues.append(ValidationIssue(
                        cell=full_cell,
                        rule="not_empty",
                        severity="error",
                        message="Cell must not be empty",
                        expected="non-empty value",
                        actual=value,
                    ))
                    failed += 1
                else:
                    passed += 1

            # Check allowed_values (enum)
            allowed = rules.get("allowed_values")
            if allowed and value is not None:
                total_checks += 1
                if value not in allowed:
                    issues.append(ValidationIssue(
                        cell=full_cell,
                        rule="allowed_values",
                        severity="error",
                        message=f"Value '{value}' not in allowed values: {allowed}",
                        expected=allowed,
                        actual=value,
                    ))
                    failed += 1
                else:
                    passed += 1

    success = failed == 0
    return ValidationResult(
        success=success,
        total_checks=total_checks,
        passed=passed,
        failed=failed,
        warnings=warnings,
        issues=issues,
    )


def _validate_type(
    cell: str,
    value: Any,
    formula: str,
    expected_type: str,
) -> Optional[ValidationIssue]:
    """Validate that a cell has the expected type."""
    if expected_type == "formula":
        if not (formula and formula.startswith("=")):
            return ValidationIssue(
                cell=cell,
                rule="type",
                severity="error",
                message=f"Expected formula, got value: {value}",
                expected="formula",
                actual=type(value).__name__,
            )
    elif expected_type == "number":
        if not isinstance(value, (int, float)):
            return ValidationIssue(
                cell=cell,
                rule="type",
                severity="error",
                message=f"Expected number, got: {value}",
                expected="number",
                actual=type(value).__name__,
            )
    elif expected_type == "string":
        if not isinstance(value, str):
            return ValidationIssue(
                cell=cell,
                rule="type",
                severity="error",
                message=f"Expected string, got: {value}",
                expected="string",
                actual=type(value).__name__,
            )
    elif expected_type == "boolean":
        if not isinstance(value, bool):
            return ValidationIssue(
                cell=cell,
                rule="type",
                severity="error",
                message=f"Expected boolean, got: {value}",
                expected="boolean",
                actual=type(value).__name__,
            )
    elif expected_type == "date":
        # Google Sheets dates are often numbers (serial date) or strings
        if not isinstance(value, (int, float, str)):
            return ValidationIssue(
                cell=cell,
                rule="type",
                severity="error",
                message=f"Expected date, got: {value}",
                expected="date",
                actual=type(value).__name__,
            )
    return None


def _col_index_to_letter(index: int) -> str:
    """Convert 0-indexed column number to letter(s)."""
    result = ""
    index += 1
    while index > 0:
        index, remainder = divmod(index - 1, 26)
        result = chr(65 + remainder) + result
    return result


def format_validation_result(result: ValidationResult) -> str:
    """Format validation result for CLI output."""
    lines = []

    if result.success:
        lines.append(f"[OK] Validation PASSED: {result.passed}/{result.total_checks} checks passed")
    else:
        lines.append(f"[X] Validation FAILED: {result.failed} errors, {result.warnings} warnings")

    if result.issues:
        lines.append("")
        lines.append("Issues:")
        for issue in result.issues:
            icon = "[X]" if issue.severity == "error" else "[!]"
            lines.append(f"  {icon} {issue.cell} [{issue.rule}]: {issue.message}")

    return "\n".join(lines)
