"""
Styling utilities for Google Sheets.

Provides functions for applying formatting to sheet ranges.
"""

from __future__ import annotations

import json
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, List, Optional, Union

from g_gremlin import google_client
from g_gremlin.a1 import parse_a1_range

try:
    import yaml
    HAS_YAML = True
except ImportError:
    HAS_YAML = False


@dataclass
class StyleSpec:
    """Specification for styling a range."""
    range: str  # A1 notation or "body" for auto-expand
    header: bool = False
    bold: bool = False
    background: Optional[str] = None  # Hex color like "#4285F4"
    font_color: Optional[str] = None  # Hex color
    zebra: Optional[str] = None  # Stripe color for even rows
    font_size: Optional[int] = None
    freeze_rows: Optional[int] = None
    freeze_cols: Optional[int] = None


@dataclass
class StyleResult:
    """Result of style application."""
    success: bool
    styles_applied: int
    errors: List[str]


def hex_to_color(hex_str: str) -> Dict[str, float]:
    """Convert hex color string to Google Sheets color dict."""
    hex_str = hex_str.lstrip("#")
    if len(hex_str) == 3:
        hex_str = "".join(c * 2 for c in hex_str)
    if len(hex_str) != 6:
        return {"red": 1.0, "green": 1.0, "blue": 1.0}
    try:
        r = int(hex_str[0:2], 16) / 255.0
        g = int(hex_str[2:4], 16) / 255.0
        b = int(hex_str[4:6], 16) / 255.0
        return {"red": r, "green": g, "blue": b}
    except ValueError:
        return {"red": 1.0, "green": 1.0, "blue": 1.0}


def load_style_spec(spec_path: Union[str, Path]) -> List[StyleSpec]:
    """
    Load style specifications from a YAML or JSON file.

    Args:
        spec_path: Path to the spec file

    Returns:
        List of StyleSpec objects
    """
    path = Path(spec_path)
    ext = path.suffix.lower()

    if ext in (".yaml", ".yml"):
        if not HAS_YAML:
            raise ValueError("PyYAML required for YAML files")
        with path.open("r", encoding="utf-8") as f:
            raw = yaml.safe_load(f)
    elif ext == ".json":
        with path.open("r", encoding="utf-8") as f:
            raw = json.load(f)
    else:
        raise ValueError(f"Unsupported file extension: {ext}")

    # Handle different formats
    if isinstance(raw, list):
        styles = raw
    elif isinstance(raw, dict) and "styles" in raw:
        styles = raw["styles"]
    else:
        raise ValueError("Spec must be a list or dict with 'styles' key")

    return [_parse_style_spec(s) for s in styles]


def _parse_style_spec(data: Dict[str, Any]) -> StyleSpec:
    """Parse a style spec from dict."""
    return StyleSpec(
        range=data.get("range", ""),
        header=data.get("header", False),
        bold=data.get("bold", False),
        background=data.get("background"),
        font_color=data.get("font_color"),
        zebra=data.get("zebra"),
        font_size=data.get("font_size"),
        freeze_rows=data.get("freeze_rows"),
        freeze_cols=data.get("freeze_cols"),
    )


def apply_styles(
    spreadsheet_id: str,
    sheet_name: str,
    styles: List[StyleSpec],
    data_rows: Optional[int] = None,  # For "body" range auto-expand
) -> StyleResult:
    """
    Apply multiple style specs to a sheet.

    Args:
        spreadsheet_id: The spreadsheet ID
        sheet_name: Name of the sheet to style
        styles: List of StyleSpec objects
        data_rows: Number of data rows (for "body" range auto-expand)

    Returns:
        StyleResult with success/failure info
    """
    if not styles:
        return StyleResult(success=True, styles_applied=0, errors=[])

    service = google_client.get_sheets_service()
    errors: List[str] = []

    # Get sheet metadata
    try:
        sheet_id, row_count, col_count = _get_sheet_metadata(
            service, spreadsheet_id, sheet_name
        )
    except Exception as e:
        return StyleResult(success=False, styles_applied=0, errors=[str(e)])

    requests: List[Dict[str, Any]] = []

    for style in styles:
        try:
            style_requests = _build_style_requests(
                style, sheet_id, sheet_name, row_count, col_count, data_rows
            )
            requests.extend(style_requests)
        except Exception as e:
            errors.append(f"Error building style for range '{style.range}': {e}")

    if not requests:
        return StyleResult(
            success=len(errors) == 0,
            styles_applied=0,
            errors=errors,
        )

    try:
        service.spreadsheets().batchUpdate(
            spreadsheetId=spreadsheet_id,
            body={"requests": requests},
        ).execute()
        return StyleResult(
            success=True,
            styles_applied=len(styles),
            errors=errors,
        )
    except Exception as e:
        errors.append(str(e))
        return StyleResult(success=False, styles_applied=0, errors=errors)


def _build_style_requests(
    style: StyleSpec,
    sheet_id: int,
    sheet_name: str,
    row_count: int,
    col_count: int,
    data_rows: Optional[int] = None,
) -> List[Dict[str, Any]]:
    """Build API requests for a single style spec."""
    requests: List[Dict[str, Any]] = []

    # Resolve range
    if style.range.lower() == "body":
        # Auto-expand to data rows (excluding header)
        start_row = 1  # 0-indexed, row 2
        end_row = data_rows if data_rows else row_count
        grid_range = {
            "sheetId": sheet_id,
            "startRowIndex": start_row,
            "endRowIndex": end_row,
            "startColumnIndex": 0,
            "endColumnIndex": col_count,
        }
    else:
        # Parse A1 range
        range_with_sheet = style.range
        if "!" not in range_with_sheet:
            range_with_sheet = f"'{sheet_name}'!{style.range}"

        parsed = parse_a1_range(range_with_sheet)
        grid_range = _build_grid_range(parsed, sheet_id, row_count, col_count)

    # Header styling
    if style.header or style.bold or style.background or style.font_color:
        format_dict: Dict[str, Any] = {}

        if style.background:
            format_dict["backgroundColor"] = hex_to_color(style.background)

        text_format: Dict[str, Any] = {}
        if style.bold:
            text_format["bold"] = True
        if style.font_color:
            text_format["foregroundColor"] = hex_to_color(style.font_color)
        if style.font_size:
            text_format["fontSize"] = style.font_size

        if text_format:
            format_dict["textFormat"] = text_format

        if format_dict:
            fields = []
            if "backgroundColor" in format_dict:
                fields.append("userEnteredFormat.backgroundColor")
            if "textFormat" in format_dict:
                fields.append("userEnteredFormat.textFormat")

            requests.append({
                "repeatCell": {
                    "range": grid_range,
                    "cell": {"userEnteredFormat": format_dict},
                    "fields": ",".join(fields),
                }
            })

    # Zebra striping
    if style.zebra:
        requests.append({
            "addConditionalFormatRule": {
                "rule": {
                    "ranges": [grid_range],
                    "booleanRule": {
                        "condition": {
                            "type": "CUSTOM_FORMULA",
                            "values": [{"userEnteredValue": "=ISEVEN(ROW())"}],
                        },
                        "format": {
                            "backgroundColor": hex_to_color(style.zebra),
                        },
                    },
                },
                "index": 0,
            }
        })

    # Freeze rows/cols
    if style.freeze_rows is not None or style.freeze_cols is not None:
        grid_props: Dict[str, Any] = {}
        if style.freeze_rows is not None:
            grid_props["frozenRowCount"] = style.freeze_rows
        if style.freeze_cols is not None:
            grid_props["frozenColumnCount"] = style.freeze_cols

        requests.append({
            "updateSheetProperties": {
                "properties": {
                    "sheetId": sheet_id,
                    "gridProperties": grid_props,
                },
                "fields": "gridProperties.frozenRowCount,gridProperties.frozenColumnCount",
            }
        })

    return requests


def _get_sheet_metadata(
    service,
    spreadsheet_id: str,
    sheet_name: str,
) -> tuple:
    """Get sheet ID, row count, and column count."""
    response = (
        service.spreadsheets()
        .get(
            spreadsheetId=spreadsheet_id,
            fields="sheets.properties.sheetId,sheets.properties.title,sheets.properties.gridProperties",
        )
        .execute()
    )

    for sheet in response.get("sheets", []):
        props = sheet.get("properties", {})
        if props.get("title") == sheet_name:
            grid_props = props.get("gridProperties", {})
            return (
                props.get("sheetId"),
                grid_props.get("rowCount", 1000),
                grid_props.get("columnCount", 26),
            )

    raise ValueError(f"Sheet '{sheet_name}' not found")


def _build_grid_range(
    parsed,
    sheet_id: int,
    row_count: int,
    col_count: int,
) -> Dict[str, Any]:
    """Build a GridRange dict from parsed A1 range."""
    grid_range: Dict[str, Any] = {"sheetId": sheet_id}

    if parsed.start_row:
        grid_range["startRowIndex"] = parsed.start_row - 1
    else:
        grid_range["startRowIndex"] = 0

    if parsed.end_row:
        grid_range["endRowIndex"] = parsed.end_row
    else:
        grid_range["endRowIndex"] = row_count

    if parsed.start_col:
        grid_range["startColumnIndex"] = parsed.start_col - 1
    else:
        grid_range["startColumnIndex"] = 0

    if parsed.end_col:
        grid_range["endColumnIndex"] = parsed.end_col
    else:
        grid_range["endColumnIndex"] = col_count

    return grid_range
