"""
Build a Pipeline Coverage & Timing model in Google Sheets.

Question answered: When must pipeline be created to close by target quarter?
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, List, Optional

from .period_helper import PeriodConfig, build_period_table


def _col_letter(idx: int) -> str:
    """Convert zero-based index to Excel-style column letter."""
    result = ""
    idx_val = idx
    while True:
        idx_val, rem = divmod(idx_val, 26)
        result = chr(ord("A") + rem) + result
        if idx_val == 0:
            break
        idx_val -= 1
    return result


@dataclass
class CoverageInputs:
    """Inputs for the Pipeline Coverage & Timing model."""
    annual_target: float
    pacing_q1: float = 0.15
    pacing_q2: float = 0.20
    pacing_q3: float = 0.30
    pacing_q4: float = 0.35
    avg_deal_size: float = 100000.0
    coverage_target: float = 3.0
    win_rate: float = 0.25
    sales_cycle_days: int = 90
    late_threshold_days: int = 30
    # Optional stage breakdown
    use_stage_timing: bool = False
    stage_1_name: str = "Discovery"
    stage_1_days: int = 20
    stage_2_name: str = "Qualification"
    stage_2_days: int = 25
    stage_3_name: str = "Proposal"
    stage_3_days: int = 25
    stage_4_name: str = "Negotiation"
    stage_4_days: int = 20
    # Period settings
    first_fy: int = 26
    fy_start_month: int = 1
    planning_years: int = 1  # 1–3 fiscal years to plan
    yoy_growth_pct: float = 0.0
    as_of_date: str = ""
    # Idempotency handling
    tab_suffix: str = ""  # e.g., "_v2" to append to tab names
    idempotency_action: str = ""  # "replace" or "suffix"

    @property
    def pacing(self) -> List[float]:
        return [self.pacing_q1, self.pacing_q2, self.pacing_q3, self.pacing_q4]

    @property
    def horizon_periods(self) -> int:
        return self.planning_years * 4

    @property
    def stage_durations(self) -> List[tuple]:
        """Return list of (name, days) tuples for stages."""
        return [
            (self.stage_1_name, self.stage_1_days),
            (self.stage_2_name, self.stage_2_days),
            (self.stage_3_name, self.stage_3_days),
            (self.stage_4_name, self.stage_4_days),
        ]

    def __post_init__(self) -> None:
        # Validate pacing sums to approximately 100%
        pacing_sum = sum(self.pacing)
        if not (0.95 <= pacing_sum <= 1.05):
            raise ValueError(f"Quarterly pacing must sum to ~100%, got {pacing_sum * 100:.1f}%")

        if self.fy_start_month < 1 or self.fy_start_month > 12:
            raise ValueError("fy_start_month must be between 1 and 12.")

        if self.late_threshold_days >= self.sales_cycle_days:
            raise ValueError("late_threshold_days must be less than sales_cycle_days")

        if self.use_stage_timing:
            stage_total = sum(d for _, d in self.stage_durations)
            if stage_total > self.sales_cycle_days:
                raise ValueError(f"Stage durations ({stage_total}d) exceed sales cycle ({self.sales_cycle_days}d)")


def _assumptions_row_map() -> Dict[str, int]:
    """Compute 1-based sheet row numbers for Coverage assumptions cells.

    Layout mirrors _build_assumptions_rows exactly.
    """
    r = 1
    # README section (4 rows + blank)
    r += 4  # header, what, assumptions, FY convention
    r += 1  # blank
    # TARGETS section
    r += 1  # section header
    pos: Dict[str, int] = {}
    pos["annual_target"] = r; r += 1
    pos["pacing_q1"] = r; r += 1
    pos["pacing_q2"] = r; r += 1
    pos["pacing_q3"] = r; r += 1
    pos["pacing_q4"] = r; r += 1
    r += 1  # blank
    # PIPELINE SETTINGS section
    r += 1  # section header
    pos["avg_deal_size"] = r; r += 1
    pos["coverage_target"] = r; r += 1
    pos["win_rate"] = r; r += 1
    r += 1  # blank
    # TIMING section
    r += 1  # section header
    pos["sales_cycle_days"] = r; r += 1
    pos["late_threshold_days"] = r; r += 1
    r += 1  # blank
    # STAGE BREAKDOWN section
    r += 1  # section header
    pos["use_stage_timing"] = r; r += 1
    pos["stage_1_days"] = r; r += 1
    pos["stage_2_days"] = r; r += 1
    pos["stage_3_days"] = r; r += 1
    pos["stage_4_days"] = r; r += 1
    r += 1  # blank
    # PERIOD SETTINGS section
    r += 1  # section header
    pos["first_fy"] = r; r += 1
    pos["fy_start_month"] = r; r += 1
    pos["planning_years"] = r; r += 1
    pos["yoy_growth_pct"] = r; r += 1
    pos["as_of_date"] = r
    return pos


def _quote_tab(tab_name: str) -> str:
    """Escape single quotes in tab names for sheet references."""
    escaped = tab_name.replace("'", "''")
    return f"'{escaped}'"


def _sheet_cell(tab_name: str, cell_ref: str) -> str:
    """Build a quoted sheet cell reference."""
    return f"{_quote_tab(tab_name)}!{cell_ref}"


def _hex_to_color(hex_value: str) -> Dict[str, float]:
    """Convert hex color to RGB normalized to 0-1."""
    hex_value = hex_value.lstrip("#")
    r = int(hex_value[0:2], 16) / 255.0
    g = int(hex_value[2:4], 16) / 255.0
    b = int(hex_value[4:6], 16) / 255.0
    return {"red": r, "green": g, "blue": b}


def build_pipeline_coverage(
    inputs: CoverageInputs,
    sheets_service,
    spreadsheet_id: Optional[str] = None,
    title: str = "Pipeline Coverage & Timing",
) -> Dict[str, Any]:
    """
    Build the Pipeline Coverage & Timing model.

    Creates 3 tabs:
    - Coverage_Assumptions: User inputs with readme panel
    - Coverage_Calc: Calculations with period columns
    - Coverage_Dashboard: Key outputs and RAG status

    Args:
        inputs: CoverageInputs with all model parameters
        sheets_service: Google Sheets API service client
        spreadsheet_id: Optional existing spreadsheet to update
        title: Title for new spreadsheet if created

    Returns:
        Dict with spreadsheet_id, url, tabs_created, validation status
    """
    # Apply tab suffix if provided (for idempotency with suffix mode)
    suffix = inputs.tab_suffix or ""
    assumptions_tab = f"Coverage_Assumptions{suffix}"
    calc_tab = f"Coverage_Calc{suffix}"
    dashboard_tab = f"Coverage_Dashboard{suffix}"

    # Determine if we should replace existing tabs
    replace_existing = inputs.idempotency_action == "replace"

    # Generate period labels
    period_config = PeriodConfig(
        first_fy=inputs.first_fy,
        fy_start_month=inputs.fy_start_month,
        granularity="quarterly",
        horizon_periods=inputs.horizon_periods,
    )
    periods = build_period_table(period_config)

    # Create or get spreadsheet
    if spreadsheet_id is None:
        create_resp = sheets_service.spreadsheets().create(
            body={"properties": {"title": title}}
        ).execute()
        spreadsheet_id = create_resp["spreadsheetId"]
        created = True
    else:
        created = False

    # Ensure tabs exist (replace if idempotency action is "replace")
    _ensure_sheet(sheets_service, spreadsheet_id, assumptions_tab, replace=replace_existing)
    _ensure_sheet(sheets_service, spreadsheet_id, calc_tab, replace=replace_existing)
    _ensure_sheet(sheets_service, spreadsheet_id, dashboard_tab, replace=replace_existing)

    # Write Assumptions tab
    assumptions_rows = _build_assumptions_rows(inputs)
    _write_rows(sheets_service, spreadsheet_id, assumptions_tab, assumptions_rows)

    # Write Calc tab
    calc_rows = _build_calc_rows(inputs, periods, assumptions_tab)
    _write_rows(sheets_service, spreadsheet_id, calc_tab, calc_rows)

    # Write Dashboard tab
    dashboard_rows = _build_dashboard_rows(inputs, periods, calc_tab)
    _write_rows(sheets_service, spreadsheet_id, dashboard_tab, dashboard_rows)

    # Apply styling
    _apply_coverage_style(sheets_service, spreadsheet_id, assumptions_tab, calc_tab, dashboard_tab, len(periods))

    # Validate
    validation = _validate_coverage(sheets_service, spreadsheet_id, calc_tab, len(periods))

    url = f"https://docs.google.com/spreadsheets/d/{spreadsheet_id}"

    return {
        "spreadsheet_id": spreadsheet_id,
        "url": url,
        "tabs_created": [assumptions_tab, calc_tab, dashboard_tab],
        "created": created,
        "validation": validation,
    }


def _ensure_sheet(service, spreadsheet_id: str, sheet_name: str, replace: bool = False) -> int:
    """Ensure a sheet exists, creating it if needed. Returns sheet ID.

    Args:
        service: Sheets API service
        spreadsheet_id: Spreadsheet ID
        sheet_name: Name of the sheet
        replace: If True, delete existing sheet first
    """
    try:
        ss = service.spreadsheets().get(spreadsheetId=spreadsheet_id).execute()
        for sheet in ss.get("sheets", []):
            if sheet["properties"]["title"] == sheet_name:
                if replace:
                    # Delete existing sheet
                    sheet_id = sheet["properties"]["sheetId"]
                    service.spreadsheets().batchUpdate(
                        spreadsheetId=spreadsheet_id,
                        body={
                            "requests": [
                                {"deleteSheet": {"sheetId": sheet_id}}
                            ]
                        }
                    ).execute()
                    break  # Fall through to create new sheet
                else:
                    return sheet["properties"]["sheetId"]
    except Exception:
        pass

    # Create the sheet
    resp = service.spreadsheets().batchUpdate(
        spreadsheetId=spreadsheet_id,
        body={
            "requests": [
                {"addSheet": {"properties": {"title": sheet_name}}}
            ]
        }
    ).execute()

    return resp["replies"][0]["addSheet"]["properties"]["sheetId"]


def _write_rows(service, spreadsheet_id: str, sheet_name: str, rows: List[List[Any]]) -> None:
    """Write rows to a sheet."""
    if not rows:
        return

    # Determine range
    num_cols = max(len(row) for row in rows)
    end_col = _col_letter(num_cols - 1)
    range_str = f"{sheet_name}!A1:{end_col}{len(rows)}"

    # Pad rows to same length
    padded_rows = []
    for row in rows:
        padded = list(row) + [""] * (num_cols - len(row))
        padded_rows.append(padded)

    service.spreadsheets().values().update(
        spreadsheetId=spreadsheet_id,
        range=range_str,
        valueInputOption="USER_ENTERED",
        body={"values": padded_rows}
    ).execute()


def _build_assumptions_rows(inputs: CoverageInputs) -> List[List[Any]]:
    """Build the Assumptions tab content."""
    rows = []

    # README section
    rows.append(["PIPELINE COVERAGE & TIMING - ASSUMPTIONS"])
    rows.append(["What this model answers:", "When must pipeline be created to close by target quarter?"])
    rows.append(["Assumptions you can change:", "Target, coverage, sales cycle, late threshold"])
    rows.append(["FY convention:", "FY label uses the fiscal year end year (e.g., FY26 ends in 2026)"])
    rows.append([])

    # TARGETS section
    rows.append(["TARGETS"])
    rows.append(["Annual Bookings Target", inputs.annual_target])
    rows.append(["Q1 Pacing", inputs.pacing_q1])
    rows.append(["Q2 Pacing", inputs.pacing_q2])
    rows.append(["Q3 Pacing", inputs.pacing_q3])
    rows.append(["Q4 Pacing", inputs.pacing_q4])
    rows.append([])

    # PIPELINE section
    rows.append(["PIPELINE SETTINGS"])
    rows.append(["Avg Deal Size", inputs.avg_deal_size])
    rows.append(["Coverage Target (x)", inputs.coverage_target])
    rows.append(["Win Rate", inputs.win_rate])
    rows.append([])

    # TIMING section
    rows.append(["TIMING"])
    rows.append(["Sales Cycle (days)", inputs.sales_cycle_days])
    rows.append(["Late Threshold (days)", inputs.late_threshold_days])
    rows.append([])

    # STAGE BREAKDOWN section (optional)
    rows.append(["STAGE BREAKDOWN (optional)"])
    rows.append(["Use Stage Timing", "Yes" if inputs.use_stage_timing else "No"])
    rows.append([f"Stage 1: {inputs.stage_1_name}", inputs.stage_1_days, "days"])
    rows.append([f"Stage 2: {inputs.stage_2_name}", inputs.stage_2_days, "days"])
    rows.append([f"Stage 3: {inputs.stage_3_name}", inputs.stage_3_days, "days"])
    rows.append([f"Stage 4: {inputs.stage_4_name}", inputs.stage_4_days, "days"])
    rows.append([])

    # PERIOD SETTINGS section
    rows.append(["PERIOD SETTINGS"])
    rows.append(["First Fiscal Year", inputs.first_fy])
    rows.append(["FY Start Month", inputs.fy_start_month])
    rows.append(["Planning Years", inputs.planning_years])
    rows.append(["YoY Growth %", inputs.yoy_growth_pct])
    rows.append(["As-Of Date", inputs.as_of_date or ""])

    return rows


def _build_calc_rows(inputs: CoverageInputs, periods: list, assumptions_tab: str) -> List[List[Any]]:
    """Build the Calc tab with formulas."""
    rows = []
    num_periods = len(periods)
    rm = _assumptions_row_map()

    def _a(key: str) -> str:
        """Return absolute $B$N reference into assumptions tab."""
        return _sheet_cell(assumptions_tab, f"$B${rm[key]}")

    # Header row with period labels
    header = ["Type", "Category", "Metric"]
    for p in periods:
        header.append(p.label)
    rows.append(header)

    # Period dates (written as values, not formulas)
    start_row = ["", "", "Period Start"]
    end_row = ["", "", "Period End"]
    for p in periods:
        start_row.append(p.start_date.isoformat())
        end_row.append(p.end_date.isoformat())
    rows.append(start_row)
    rows.append(end_row)

    rows.append([])  # Blank row

    # TARGETS section
    rows.append(["TARGETS", "", ""])
    target_row = ["Target", "ACV", "Bookings Target"]
    for i, p in enumerate(periods):
        fy = p.fy
        qtr_in_fy = p.period_num
        # Determine annual target for this FY
        yr_offset = fy - inputs.first_fy
        if yr_offset == 0:
            annual_cell = _a("annual_target")
        else:
            # Chained growth: FY(N+k) = base * (1 + growth)^k
            annual_cell = f"({_a('annual_target')}*(1+{_a('yoy_growth_pct')})^{yr_offset})"

        # Pacing cell
        pacing_row = rm["pacing_q1"] + (qtr_in_fy - 1)
        pacing_cell = _sheet_cell(assumptions_tab, f"$B${pacing_row}")

        target_row.append(f"={annual_cell}*{pacing_cell}")
    rows.append(target_row)

    rows.append([])  # Blank row

    # PIPELINE REQUIREMENTS section
    rows.append(["PIPELINE", "", ""])

    # Mathematical Pipeline = Target / WinRate
    math_pipeline_row = ["Pipeline", "Math", "Mathematical Pipeline"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        math_pipeline_row.append(f"={col}6/{_a('win_rate')}")
    rows.append(math_pipeline_row)

    # Heuristic Pipeline = Target × CoverageTarget
    heuristic_pipeline_row = ["Pipeline", "Heuristic", "Heuristic Pipeline"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        heuristic_pipeline_row.append(f"={col}6*{_a('coverage_target')}")
    rows.append(heuristic_pipeline_row)

    # Recommended Pipeline = MAX(Mathematical, Heuristic)
    recommended_row = ["Pipeline", "Recommended", "Recommended Pipeline"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        recommended_row.append(f"=MAX({col}9,{col}10)")
    rows.append(recommended_row)

    # Required Opps = Target / (ASP × WinRate)
    opps_row = ["Pipeline", "Count", "Required Opps"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        opps_row.append(f"={col}6/({_a('avg_deal_size')}*{_a('win_rate')})")
    rows.append(opps_row)

    rows.append([])  # Blank row

    # DEADLINES section
    rows.append(["DEADLINES", "", ""])

    # Close Date = Period End
    close_date_row = ["Deadline", "Date", "Close Date"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        close_date_row.append(f"={col}3")
    rows.append(close_date_row)

    # Creation Deadline = Close Date - Sales Cycle Days
    creation_deadline_row = ["Deadline", "Date", "Creation Deadline"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        creation_deadline_row.append(f"={col}15-{_a('sales_cycle_days')}")
    rows.append(creation_deadline_row)

    # Late Pipeline Cutoff = Close Date - Late Threshold
    late_cutoff_row = ["Deadline", "Date", "Late Pipeline Cutoff"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        late_cutoff_row.append(f"={col}15-{_a('late_threshold_days')}")
    rows.append(late_cutoff_row)

    # Currently Late? = IF(AsOfDate > CreationDeadline, "LATE", "OK")
    late_status_row = ["Deadline", "Status", "Currently Late?"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        late_status_row.append(f'=IF({_a("as_of_date")}>{col}16,"LATE","OK")')
    rows.append(late_status_row)

    rows.append([])  # Blank row

    # COVERAGE HEALTH section
    rows.append(["COVERAGE", "", ""])

    # Current Pipeline (input area - user enters values)
    current_pipeline_row = ["Coverage", "Input", "Current Pipeline (enter)"]
    for i in range(num_periods):
        current_pipeline_row.append(0)  # Default to 0, user will enter
    rows.append(current_pipeline_row)

    # Current Coverage = Current Pipeline / Target
    current_coverage_row = ["Coverage", "Ratio", "Current Coverage"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        current_coverage_row.append(f"=IFERROR({col}21/{col}6,0)")
    rows.append(current_coverage_row)

    # Pipeline Gap = Recommended - Current
    gap_row = ["Coverage", "ACV", "Pipeline Gap"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        gap_row.append(f"={col}11-{col}21")
    rows.append(gap_row)

    # Coverage Status = RAG based on coverage vs target
    coverage_status_row = ["Coverage", "Status", "Coverage Status"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        coverage_status_row.append(f'=IF({col}22>={_a("coverage_target")},"GREEN",IF({col}22>={_a("coverage_target")}*0.7,"YELLOW","RED"))')
    rows.append(coverage_status_row)

    # Stage timing section (if enabled)
    if inputs.use_stage_timing:
        rows.append([])
        rows.append(["STAGES", "", ""])

        stage_day_cells = [
            f"$B${rm['stage_1_days']}",
            f"$B${rm['stage_2_days']}",
            f"$B${rm['stage_3_days']}",
            f"$B${rm['stage_4_days']}",
        ]
        for idx, (stage_name, _) in enumerate(inputs.stage_durations):
            stage_deadline_row = ["Stage", "Date", f"Enter {stage_name} By"]
            for i in range(num_periods):
                col = _col_letter(3 + i)
                if idx == 0:
                    formula = f"={col}15-{_a('sales_cycle_days')}"
                else:
                    prior_stages = "+".join([_sheet_cell(assumptions_tab, cell) for cell in stage_day_cells[:idx]])
                    formula = f"={col}15-{_a('sales_cycle_days')}+{prior_stages}"
                stage_deadline_row.append(formula)
            rows.append(stage_deadline_row)

    return rows


def _build_dashboard_rows(inputs: CoverageInputs, periods: list, calc_tab: str) -> List[List[Any]]:
    """Build the Dashboard tab with key metrics."""
    rows = []
    num_periods = len(periods)

    # Header
    header = ["Metric"]
    for p in periods:
        header.append(p.label)
    rows.append(header)

    rows.append([])  # Blank row

    # Key metrics
    rows.append(["KEY METRICS"])

    # Bookings Target
    target_row = ["Bookings Target"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        target_row.append(f"={_sheet_cell(calc_tab, f'{col}6')}")
    rows.append(target_row)

    # Recommended Pipeline
    pipeline_row = ["Recommended Pipeline"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        pipeline_row.append(f"={_sheet_cell(calc_tab, f'{col}11')}")
    rows.append(pipeline_row)

    # Required Opps
    opps_row = ["Required Opps"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        opps_row.append(f"={_sheet_cell(calc_tab, f'{col}12')}")
    rows.append(opps_row)

    rows.append([])

    # TIMING
    rows.append(["TIMING"])

    # Creation Deadline
    deadline_row = ["Creation Deadline"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        deadline_row.append(f"={_sheet_cell(calc_tab, f'{col}16')}")
    rows.append(deadline_row)

    # Currently Late?
    late_row = ["Currently Late?"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        late_row.append(f"={_sheet_cell(calc_tab, f'{col}18')}")
    rows.append(late_row)

    rows.append([])

    # COVERAGE STATUS
    rows.append(["COVERAGE"])

    # Current Coverage
    current_cov_row = ["Current Coverage"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        current_cov_row.append(f"={_sheet_cell(calc_tab, f'{col}22')}")
    rows.append(current_cov_row)

    # Coverage Status
    status_row = ["Status"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        status_row.append(f"={_sheet_cell(calc_tab, f'{col}24')}")
    rows.append(status_row)

    rows.append([])  # Blank row

    # SANITY CHECK section (self-diagnostic)
    rows.append(["SANITY CHECK"])
    # Check: Recommended Pipeline >= MAX(Math, Heuristic)
    # Calc rows: Math=9, Heuristic=10, Recommended=11
    check_row = ["Check (Rec ≥ Math & Heur)"]
    for i in range(num_periods):
        col = _col_letter(3 + i)
        math_cell = _sheet_cell(calc_tab, f'{col}9')
        heur_cell = _sheet_cell(calc_tab, f'{col}10')
        rec_cell = _sheet_cell(calc_tab, f'{col}11')
        check_row.append(f'=IF(AND({rec_cell}>={math_cell},{rec_cell}>={heur_cell}),"✓","✗")')
    rows.append(check_row)

    return rows


def _get_sheet_id(service, spreadsheet_id: str, sheet_name: str) -> int:
    """Get the sheet ID for a named sheet."""
    ss = service.spreadsheets().get(spreadsheetId=spreadsheet_id).execute()
    for sheet in ss.get("sheets", []):
        if sheet["properties"]["title"] == sheet_name:
            return sheet["properties"]["sheetId"]
    raise ValueError(f"Sheet {sheet_name} not found")


def _apply_coverage_style(
    service,
    spreadsheet_id: str,
    assumptions_tab: str,
    calc_tab: str,
    dashboard_tab: str,
    num_periods: int
) -> None:
    """Apply professional formatting to all tabs."""
    requests = []

    # Style each tab
    for tab_name in [assumptions_tab, calc_tab, dashboard_tab]:
        try:
            sheet_id = _get_sheet_id(service, spreadsheet_id, tab_name)
        except ValueError:
            continue

        # Freeze first row and first 3 columns
        requests.append({
            "updateSheetProperties": {
                "properties": {
                    "sheetId": sheet_id,
                    "gridProperties": {
                        "frozenRowCount": 1,
                        "frozenColumnCount": 3 if tab_name == calc_tab else 1,
                    }
                },
                "fields": "gridProperties.frozenRowCount,gridProperties.frozenColumnCount",
            }
        })

        # Header styling (row 1)
        end_col = 3 + num_periods if tab_name in [calc_tab, dashboard_tab] else 4
        requests.append({
            "repeatCell": {
                "range": {
                    "sheetId": sheet_id,
                    "startRowIndex": 0,
                    "endRowIndex": 1,
                    "startColumnIndex": 0,
                    "endColumnIndex": end_col,
                },
                "cell": {
                    "userEnteredFormat": {
                        "backgroundColor": _hex_to_color("#0f172a"),
                        "textFormat": {
                            "bold": True,
                            "foregroundColor": _hex_to_color("#ffffff"),
                        },
                    }
                },
                "fields": "userEnteredFormat(backgroundColor,textFormat)",
            }
        })

        # Date formatting: MMM d, yyyy
        if tab_name == calc_tab:
            # Period Start/End rows (rows 2-3, 0-indexed 1-2)
            # Also deadline rows: Close Date (15), Creation Deadline (16), Late Cutoff (17)
            for row_idx in [1, 2, 14, 15, 16]:  # 0-indexed
                requests.append({
                    "repeatCell": {
                        "range": {
                            "sheetId": sheet_id,
                            "startRowIndex": row_idx,
                            "endRowIndex": row_idx + 1,
                            "startColumnIndex": 3,
                            "endColumnIndex": 3 + num_periods,
                        },
                        "cell": {
                            "userEnteredFormat": {
                                "numberFormat": {
                                    "type": "DATE",
                                    "pattern": "MMM d, yyyy"
                                }
                            }
                        },
                        "fields": "userEnteredFormat.numberFormat",
                    }
                })
        elif tab_name == assumptions_tab:
            # As-Of Date (column B) — use row map for robustness
            as_of_row_0 = _assumptions_row_map()["as_of_date"] - 1
            requests.append({
                "repeatCell": {
                    "range": {
                        "sheetId": sheet_id,
                        "startRowIndex": as_of_row_0,
                        "endRowIndex": as_of_row_0 + 1,
                        "startColumnIndex": 1,
                        "endColumnIndex": 2,
                    },
                    "cell": {
                        "userEnteredFormat": {
                            "numberFormat": {
                                "type": "DATE",
                                "pattern": "MMM d, yyyy"
                            }
                        }
                    },
                    "fields": "userEnteredFormat.numberFormat",
                }
            })
        elif tab_name == dashboard_tab:
            # Creation Deadline row in dashboard (row 9, 0-indexed 8)
            requests.append({
                "repeatCell": {
                    "range": {
                        "sheetId": sheet_id,
                        "startRowIndex": 8,
                        "endRowIndex": 9,
                        "startColumnIndex": 1,
                        "endColumnIndex": 1 + num_periods,
                    },
                    "cell": {
                        "userEnteredFormat": {
                            "numberFormat": {
                                "type": "DATE",
                                "pattern": "MMM d, yyyy"
                            }
                        }
                    },
                    "fields": "userEnteredFormat.numberFormat",
                }
            })

    if requests:
        service.spreadsheets().batchUpdate(
            spreadsheetId=spreadsheet_id,
            body={"requests": requests}
        ).execute()


def _validate_coverage(
    service,
    spreadsheet_id: str,
    calc_tab: str,
    num_periods: int
) -> Dict[str, Any]:
    """Validate the coverage model outputs."""
    errors: List[str] = []

    try:
        # Read calc tab data
        end_col = _col_letter(3 + num_periods - 1)
        range_str = f"{calc_tab}!A1:{end_col}30"
        resp = service.spreadsheets().values().get(
            spreadsheetId=spreadsheet_id,
            range=range_str,
            valueRenderOption="UNFORMATTED_VALUE"
        ).execute()
        values = resp.get("values", [])

        # Check for LATE or RED status in any period
        for row in values:
            if len(row) >= 3:
                if row[2] == "Currently Late?":
                    for i, val in enumerate(row[3:], start=1):
                        if val == "LATE":
                            errors.append(f"Period {i}: Pipeline creation is LATE")
                elif row[2] == "Coverage Status":
                    for i, val in enumerate(row[3:], start=1):
                        if val == "RED":
                            errors.append(f"Period {i}: Coverage shortfall (RED)")

    except Exception as e:
        errors.append(f"Validation error: {str(e)}")

    return {
        "status": "ok" if not errors else "issues",
        "errors": errors,
    }
