"""
Build a territory planner model in Google Sheets.
"""

from __future__ import annotations

import logging
import math
from collections import defaultdict
from typing import Any, Dict, List, Optional, Tuple

from g_gremlin.a1 import parse_a1_range
from fmatch.core.heuristics.geo_centroids import (
    get_market_centroid,
    haversine_distance,
    parse_seed_region_to_coords,
)

logger = logging.getLogger(__name__)

ALLOWED_GEO_KEY_TYPES = {"auto", "state", "city_state", "zip"}
ALLOWED_ASSIGNMENT_MODES = {"territory", "named_account"}
ALLOWED_BALANCE_BY = {"count", "arr", "pipeline"}
UNKNOWN_MARKET = "(Unknown)"


def _inputs_to_dict(inputs: Any) -> Dict[str, Any]:
    if hasattr(inputs, "dict"):
        return inputs.dict()
    if isinstance(inputs, dict):
        return inputs
    return dict(inputs)


def _escape_soql_literal(value: str) -> str:
    return str(value).replace("\\", "\\\\").replace("'", "\\'")


def _normalize_values(values: Any) -> List[str]:
    if values is None:
        return []
    if isinstance(values, str):
        raw = values.split(",")
        return [v.strip() for v in raw if v and v.strip()]
    if isinstance(values, (list, tuple, set)):
        return [str(v).strip() for v in values if str(v).strip()]
    return [str(values).strip()] if str(values).strip() else []


def _build_filter_clause(field: str, values: List[str], field_type: str) -> str:
    field = str(field or "").strip()
    if not field:
        raise ValueError("Filter field is required.")

    ftype = (field_type or "picklist").strip().lower()
    if ftype == "boolean":
        flag = True
        if values:
            raw = str(values[0]).strip().lower()
            flag = raw in {"true", "1", "yes", "y", "t"}
        return f"{field} = {'TRUE' if flag else 'FALSE'}"

    if not values:
        raise ValueError(f"Filter values required for field '{field}'.")

    rendered: List[str] = []
    for val in values:
        raw = str(val).strip()
        if not raw:
            continue
        rendered.append(f"'{_escape_soql_literal(raw)}'")

    if not rendered:
        raise ValueError(f"Filter values required for field '{field}'.")

    return f"{field} IN ({', '.join(rendered)})"


def _title_case(value: str) -> str:
    return " ".join(word.capitalize() for word in str(value).strip().split())


def _normalize_geo_key(
    geo_key_type: str,
    city: Optional[str],
    state: Optional[str],
    postal: Optional[str],
) -> str:
    key_type = (geo_key_type or "state").strip().lower()
    city_val = str(city or "").strip()
    state_val = str(state or "").strip()
    postal_val = str(postal or "").strip()

    if key_type == "city_state":
        if city_val and state_val:
            return f"{_title_case(city_val)}, {state_val.upper()}"
        if state_val:
            return state_val.upper()
        if city_val:
            return _title_case(city_val)
        return UNKNOWN_MARKET
    if key_type == "zip":
        return postal_val if postal_val else UNKNOWN_MARKET
    if key_type == "state":
        return state_val.upper() if state_val else UNKNOWN_MARKET
    return UNKNOWN_MARKET


def _safe_float(value: Any) -> float:
    if value is None:
        return 0.0
    try:
        return float(value)
    except (TypeError, ValueError):
        return 0.0


def _col_letter(idx: int) -> str:
    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


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 _ensure_sheet(service, spreadsheet_id: str, sheet_name: str, replace: bool = False) -> int:
    try:
        ss = service.spreadsheets().get(spreadsheetId=spreadsheet_id).execute()
        for sheet in ss.get("sheets", []):
            if sheet["properties"]["title"] == sheet_name:
                if replace:
                    sheet_id = sheet["properties"]["sheetId"]
                    service.spreadsheets().batchUpdate(
                        spreadsheetId=spreadsheet_id,
                        body={"requests": [{"deleteSheet": {"sheetId": sheet_id}}]},
                    ).execute()
                    break
                return sheet["properties"]["sheetId"]
    except Exception:
        pass

    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:
    if not rows:
        return
    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)}"
    padded_rows = []
    for row in rows:
        padded_rows.append(list(row) + [""] * (num_cols - len(row)))
    service.spreadsheets().values().update(
        spreadsheetId=spreadsheet_id,
        range=range_str,
        valueInputOption="USER_ENTERED",
        body={"values": padded_rows},
    ).execute()


def _suggest_geo_key_type(rep_count: int, account_count: int) -> str:
    if rep_count <= 5:
        return "state"
    if rep_count <= 15:
        return "state" if account_count > 5000 else "city_state"
    return "zip" if account_count <= 5000 else "city_state"


def _normalize_seed(value: Optional[str]) -> str:
    return str(value or "").strip()


def _seed_matches_market(seed_region: Optional[str], market_key: str, geo_key_type: str) -> bool:
    seed = _normalize_seed(seed_region)
    if not seed:
        return False
    seed_norm = seed.lower()
    key_norm = str(market_key or "").strip().lower()
    if not key_norm:
        return False

    if geo_key_type == "state":
        return key_norm == seed_norm
    if geo_key_type == "zip":
        return key_norm.startswith(seed_norm)
    if geo_key_type == "city_state":
        if "," in seed_norm:
            return key_norm == seed_norm
        if len(seed_norm) <= 3:
            return key_norm.endswith(", " + seed_norm) or key_norm == seed_norm
        return key_norm.startswith(seed_norm) or key_norm == seed_norm

    return key_norm.startswith(seed_norm) or key_norm == seed_norm


def _calculate_distance_score(
    rep_coords: Optional[Tuple[float, float]],
    market_coords: Optional[Tuple[float, float]],
    max_distance: float = 3000.0,
) -> float:
    """Normalized distance score (0-1) where closer = higher."""
    if not rep_coords or not market_coords:
        return 0.0
    distance = haversine_distance(rep_coords[0], rep_coords[1], market_coords[0], market_coords[1])
    return max(0.0, 1.0 - (distance / max_distance))


def _pick_best_rep_with_proximity(
    rep_stats: Dict[str, Dict[str, Any]],
    rep_coords_cache: Dict[str, Optional[Tuple[float, float]]],
    market_coords: Optional[Tuple[float, float]],
    geo_weight: float = 0.7,
    candidates: Optional[List[str]] = None,
) -> Tuple[str, str]:
    """Pick best rep using distance-weighted scoring with load balancing."""
    names = candidates or list(rep_stats.keys())
    if len(names) == 1:
        return names[0], "only_candidate"

    loads = [rep_stats[name]["load"] for name in names]
    max_load = max(loads) if loads else 1.0
    if max_load == 0:
        max_load = 1.0

    max_distance = 3000.0
    if market_coords:
        distances = []
        for name in names:
            rep_coord = rep_coords_cache.get(name)
            if rep_coord:
                distances.append(
                    haversine_distance(
                        rep_coord[0],
                        rep_coord[1],
                        market_coords[0],
                        market_coords[1],
                    )
                )
        if distances:
            max_distance = max(max(distances), max_distance)

    best_rep = names[0]
    best_score = -1.0
    best_reason = "load_balance"

    for name in names:
        rep_coord = rep_coords_cache.get(name)
        distance_score = _calculate_distance_score(
            rep_coord, market_coords, max_distance=max_distance
        )
        current_load = rep_stats[name]["load"]
        load_score = 1.0 - (current_load / max_load) if max_load > 0 else 1.0

        if rep_coord and market_coords:
            combined_score = (geo_weight * distance_score) + ((1 - geo_weight) * load_score)
            reason = "geo_proximity" if distance_score > 0.5 else "geo_and_load"
        else:
            combined_score = load_score
            reason = "load_balance"

        if combined_score > best_score:
            best_score = combined_score
            best_rep = name
            best_reason = reason

    return best_rep, best_reason


def _calculate_balance_metrics(loads: List[float]) -> Dict[str, float]:
    if not loads:
        return {
            "min": 0.0,
            "max": 0.0,
            "avg": 0.0,
            "std": 0.0,
            "variance_pct": 0.0,
            "fairness": 100.0,
        }
    avg = sum(loads) / len(loads)
    min_val = min(loads)
    max_val = max(loads)
    if len(loads) > 1:
        variance = sum((val - avg) ** 2 for val in loads) / len(loads)
        std = math.sqrt(variance)
    else:
        std = 0.0
    variance_pct = (max_val - min_val) / avg if avg else 0.0
    fairness = max(0.0, 1.0 - (std / avg)) * 100 if avg else 100.0
    return {
        "min": min_val,
        "max": max_val,
        "avg": avg,
        "std": std,
        "variance_pct": variance_pct,
        "fairness": fairness,
    }


def _resolve_column_index(headers: List[str], column_name: str) -> int:
    if not column_name:
        return -1
    target = str(column_name).strip().lower()
    for idx, header in enumerate(headers):
        if str(header or "").strip().lower() == target:
            return idx
    raise ValueError(f"Column '{column_name}' not found in rep roster headers.")


def _parse_rep_roster(values: List[List[Any]], parsed_range, payload: Dict[str, Any]) -> List[Dict[str, Any]]:
    if not values:
        return []

    header_row = int(payload.get("rep_header_row", parsed_range.start_row))
    header_row = header_row if header_row > 0 else parsed_range.start_row
    header_offset = header_row - parsed_range.start_row
    if header_offset < 0 or header_offset >= len(values):
        raise ValueError("Rep header row is outside the selected range.")

    headers = values[header_offset]
    rep_name_col = str(payload.get("rep_name_col") or "").strip()
    rep_id_col = str(payload.get("rep_id_col") or "").strip()
    rep_location_col = str(payload.get("rep_location_col") or "").strip()

    if not rep_name_col:
        raise ValueError("Rep name column is required.")

    name_idx = _resolve_column_index(headers, rep_name_col)
    id_idx = _resolve_column_index(headers, rep_id_col) if rep_id_col else -1
    location_idx = _resolve_column_index(headers, rep_location_col) if rep_location_col else -1

    reps = []
    for row in values[header_offset + 1 :]:
        name = row[name_idx] if name_idx < len(row) else ""
        name = str(name or "").strip()
        if not name:
            continue
        rep_id = row[id_idx] if 0 <= id_idx < len(row) else ""
        rep_loc = row[location_idx] if 0 <= location_idx < len(row) else ""
        reps.append(
            {
                "name": name,
                "rep_id": str(rep_id or "").strip(),
                "seed_region": str(rep_loc or "").strip(),
            }
        )
    return reps


def _validate_territory_inputs(values: Dict[str, Any]) -> Tuple[str, str, str]:
    assignment_mode = str(values.get("assignment_mode") or "territory").strip().lower()
    if assignment_mode not in ALLOWED_ASSIGNMENT_MODES:
        raise ValueError("assignment_mode must be territory or named_account.")

    balance_by = str(values.get("balance_by") or "count").strip().lower()
    if balance_by not in ALLOWED_BALANCE_BY:
        raise ValueError("balance_by must be count, arr, or pipeline.")

    geo_key_type = str(values.get("geo_key_type") or "state").strip().lower()
    if geo_key_type not in ALLOWED_GEO_KEY_TYPES:
        raise ValueError("geo_key_type must be auto, state, city_state, or zip.")

    prospect_field = str(values.get("prospect_filter_field") or "").strip()
    if not prospect_field:
        raise ValueError("Prospect filter field is required.")

    prospect_values = _normalize_values(values.get("prospect_filter_values"))
    if not prospect_values:
        raise ValueError("Prospect filter values are required.")

    segment_field = str(values.get("segment_field") or "").strip()
    segment_values = _normalize_values(values.get("segment_values"))
    if segment_field and not segment_values:
        raise ValueError("Segment values are required when segment field is provided.")

    icp_field = str(values.get("icp_filter_field") or "").strip()
    icp_values = _normalize_values(values.get("icp_filter_values"))
    if icp_field and not icp_values:
        raise ValueError("ICP filter values are required when ICP filter field is provided.")

    if values.get("ultimate_parent_only"):
        parent_field = str(values.get("parent_account_field") or "").strip()
        if not parent_field:
            raise ValueError("parent_account_field is required when ultimate_parent_only is enabled.")

    return assignment_mode, balance_by, geo_key_type


async def _fetch_records(sf_client, soql: str) -> List[Dict[str, Any]]:
    records: List[Dict[str, Any]] = []
    if hasattr(sf_client, "paginate_soql"):
        async for batch in sf_client.paginate_soql(soql):
            if isinstance(batch, list):
                records.extend(batch)
            elif isinstance(batch, dict):
                records.extend(batch.get("records", []) or [])
    else:
        result = await sf_client.soql(soql)
        records.extend(result.get("records", []) or [])
    for record in records:
        record.pop("attributes", None)
    return records


def _init_rep_stats(reps: List[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]:
    rep_stats: Dict[str, Dict[str, Any]] = {}
    for rep in reps:
        name = rep["name"]
        rep_stats[name] = {
            "rep_id": rep.get("rep_id", ""),
            "seed_region": rep.get("seed_region", ""),
            "account_count": 0,
            "total_arr": 0.0,
            "total_pipeline": 0.0,
            "load": 0.0,
            "markets": set(),
            "market_counts": defaultdict(int),
        }
    return rep_stats


def _update_rep_stats(
    rep_stats: Dict[str, Dict[str, Any]],
    rep_name: str,
    account: Dict[str, Any],
    balance_by: str,
) -> None:
    stats = rep_stats[rep_name]
    stats["account_count"] += 1
    stats["total_arr"] += account.get("arr_value", 0.0)
    stats["total_pipeline"] += account.get("pipeline_value", 0.0)
    stats["market_counts"][account.get("geo_key")] += 1

    if balance_by == "arr":
        stats["load"] = stats["total_arr"]
    elif balance_by == "pipeline":
        stats["load"] = stats["total_pipeline"]
    else:
        stats["load"] = stats["account_count"]


def _pick_lowest_load(rep_stats: Dict[str, Dict[str, Any]], candidates: Optional[List[str]] = None) -> str:
    names = candidates or list(rep_stats.keys())
    return min(names, key=lambda name: (rep_stats[name]["load"], name))


def _assign_named_account_mode(
    accounts: List[Dict[str, Any]],
    reps: List[Dict[str, Any]],
    balance_by: str,
) -> Tuple[List[Dict[str, Any]], Dict[str, Dict[str, Any]]]:
    rep_stats = _init_rep_stats(reps)
    assignments: List[Dict[str, Any]] = []

    sorted_accounts = sorted(accounts, key=lambda a: (-a["value"], a["name"]))
    for account in sorted_accounts:
        rep_name = _pick_lowest_load(rep_stats)
        assignments.append(
            {
                "account": account,
                "rep_name": rep_name,
                "reason": "load_balance",
            }
        )
        _update_rep_stats(rep_stats, rep_name, account, balance_by)
        rep_stats[rep_name]["markets"].add(account.get("geo_key"))

    return assignments, rep_stats


def _assign_territory_mode(
    markets: List[Dict[str, Any]],
    reps: List[Dict[str, Any]],
    balance_by: str,
    geo_key_type: str,
    use_proximity: bool = True,
    geo_weight: float = 0.7,
) -> Tuple[List[Dict[str, Any]], Dict[str, Dict[str, Any]], Dict[str, Dict[str, Any]]]:
    """
    Assign markets to reps using territory mode with geographic proximity.

    The "amoeba" algorithm:
    1. Honor explicit seed_region matches first.
    2. For unmatched markets, use distance-weighted scoring.
    3. Fall back to load balancing if no coordinates are available.
    """
    rep_stats = _init_rep_stats(reps)
    assignments: List[Dict[str, Any]] = []
    market_assignments: Dict[str, Dict[str, Any]] = {}

    rep_coords_cache: Dict[str, Optional[Tuple[float, float]]] = {}
    for rep in reps:
        seed = rep.get("seed_region", "")
        rep_coords_cache[rep["name"]] = (
            parse_seed_region_to_coords(seed) if seed else None
        )

    any_rep_has_coords = any(coord is not None for coord in rep_coords_cache.values())

    for market in markets:
        key = market["key"]
        matched_reps = [
            rep["name"]
            for rep in reps
            if _seed_matches_market(rep.get("seed_region"), key, geo_key_type)
        ]

        if matched_reps:
            rep_name = _pick_lowest_load(rep_stats, matched_reps)
            reason = "seed_region_match"
        elif use_proximity and any_rep_has_coords:
            market_coords = get_market_centroid(key, geo_key_type)
            rep_name, reason = _pick_best_rep_with_proximity(
                rep_stats,
                rep_coords_cache,
                market_coords,
                geo_weight,
            )
        else:
            rep_name = _pick_lowest_load(rep_stats)
            reason = "load_balance"

        market_assignments[key] = {"rep_name": rep_name, "reason": reason}

        for account in market["accounts"]:
            assignments.append({"account": account, "rep_name": rep_name, "reason": reason})
            _update_rep_stats(rep_stats, rep_name, account, balance_by)
        rep_stats[rep_name]["markets"].add(key)

    return assignments, rep_stats, market_assignments


def _compute_market_counts(assignments: List[Dict[str, Any]]) -> Dict[str, Dict[str, int]]:
    counts: Dict[str, Dict[str, int]] = defaultdict(lambda: defaultdict(int))
    for row in assignments:
        rep_name = row["rep_name"]
        market = row["account"].get("geo_key")
        counts[rep_name][market] += 1
    return counts


def _build_top_markets(market_counts: Dict[str, int], limit: int = 3) -> str:
    items = sorted(market_counts.items(), key=lambda item: (-item[1], str(item[0] or "")))
    top = [f"{market} ({count})" for market, count in items[:limit] if market]
    return ", ".join(top)


def _resolve_geo_key_type(requested: str, rep_count: int, account_count: int) -> Tuple[str, bool]:
    requested = (requested or "state").strip().lower()
    if requested == "auto":
        return _suggest_geo_key_type(rep_count, account_count), True
    if requested not in ALLOWED_GEO_KEY_TYPES:
        return "state", False
    return requested, False


async def build_territory_planner(
    *,
    spreadsheet_id: Optional[str],
    inputs: Any,
    sheets_service,
    sf_client,
    title: str = "Territory Planner",
) -> Dict[str, Any]:
    """
    Build the territory planner workbook.
    """
    payload = _inputs_to_dict(inputs)
    assignment_mode, balance_by, geo_key_type = _validate_territory_inputs(payload)

    rep_sheet_tab = str(payload.get("rep_sheet_tab") or "").strip()
    rep_range_a1 = str(payload.get("rep_range_a1") or "").strip()
    if not rep_sheet_tab or not rep_range_a1:
        raise ValueError("Rep roster sheet and range are required.")

    escaped_rep_tab = rep_sheet_tab.replace("'", "''")
    rep_range = f"'{escaped_rep_tab}'!{rep_range_a1}"
    rep_values = (
        sheets_service.spreadsheets()
        .values()
        .get(spreadsheetId=payload.get("rep_spreadsheet_id") or spreadsheet_id, range=rep_range)
        .execute()
        .get("values", [])
    )

    parsed_range = parse_a1_range(rep_range)
    reps = _parse_rep_roster(rep_values, parsed_range, payload)

    if not reps:
        raise ValueError("Rep roster is empty. Add reps and try again.")

    rep_names = [rep["name"] for rep in reps]
    if len(rep_names) != len(set(rep_names)):
        raise ValueError("Rep names must be unique.")

    prospect_field = str(payload.get("prospect_filter_field") or "").strip()
    prospect_values = _normalize_values(payload.get("prospect_filter_values"))
    prospect_field_type = payload.get("prospect_filter_field_type") or "picklist"

    segment_field = str(payload.get("segment_field") or "").strip()
    segment_values = _normalize_values(payload.get("segment_values"))
    segment_field_type = payload.get("segment_field_type") or "picklist"
    icp_field = str(payload.get("icp_filter_field") or "").strip()
    icp_values = _normalize_values(payload.get("icp_filter_values"))
    icp_field_type = payload.get("icp_filter_field_type") or "boolean"
    ultimate_parent_only = bool(payload.get("ultimate_parent_only", False))
    parent_account_field = str(payload.get("parent_account_field") or "").strip()

    city_field = payload.get("city_field") or "BillingCity"
    state_field = payload.get("state_field") or "BillingState"
    zip_field = payload.get("zip_field") or "BillingPostalCode"

    arr_field = payload.get("arr_field") or None
    pipeline_field = payload.get("pipeline_field") or None

    select_fields: List[str] = [
        "Id",
        "Name",
        str(city_field),
        str(state_field),
        str(zip_field),
    ]
    if arr_field:
        select_fields.append(str(arr_field))
    if pipeline_field:
        select_fields.append(str(pipeline_field))
    select_fields = list(dict.fromkeys([f for f in select_fields if f]))

    prospect_clause = _build_filter_clause(
        prospect_field, prospect_values, prospect_field_type
    )
    where_clause = prospect_clause
    if segment_field:
        segment_clause = _build_filter_clause(segment_field, segment_values, segment_field_type)
        where_clause = f"{where_clause} AND {segment_clause}"
    if icp_field and icp_values:
        icp_clause = _build_filter_clause(icp_field, icp_values, icp_field_type)
        where_clause = f"{where_clause} AND {icp_clause}"
    if ultimate_parent_only and parent_account_field:
        where_clause = f"{where_clause} AND {parent_account_field} = NULL"

    soql = f"SELECT {', '.join(select_fields)} FROM Account WHERE {where_clause}"
    accounts = await _fetch_records(sf_client, soql)

    resolved_geo_key_type, auto_geo = _resolve_geo_key_type(
        geo_key_type, len(reps), len(accounts)
    )

    use_balance_by = balance_by
    warnings: List[str] = []
    if balance_by == "arr" and not arr_field:
        use_balance_by = "count"
        warnings.append("ARR field not provided; defaulting to count balancing.")
    if balance_by == "pipeline" and not pipeline_field:
        use_balance_by = "count"
        warnings.append("Pipeline field not provided; defaulting to count balancing.")

    normalized_accounts: List[Dict[str, Any]] = []
    for record in accounts:
        geo_key = _normalize_geo_key(
            resolved_geo_key_type,
            record.get(city_field),
            record.get(state_field),
            record.get(zip_field),
        )
        arr_value = _safe_float(record.get(arr_field)) if arr_field else 0.0
        pipeline_value = _safe_float(record.get(pipeline_field)) if pipeline_field else 0.0
        if use_balance_by == "arr":
            value = arr_value
        elif use_balance_by == "pipeline":
            value = pipeline_value
        else:
            value = 1.0
        normalized_accounts.append(
            {
                "id": record.get("Id", ""),
                "name": record.get("Name", ""),
                "geo_key": geo_key,
                "arr_value": arr_value,
                "pipeline_value": pipeline_value,
                "value": value,
            }
        )

    markets: List[Dict[str, Any]] = []
    if assignment_mode == "territory":
        grouped: Dict[str, Dict[str, Any]] = defaultdict(lambda: {"accounts": [], "total": 0.0})
        for account in normalized_accounts:
            bucket = grouped[account["geo_key"]]
            bucket["accounts"].append(account)
            bucket["total"] += account["value"]
        for key, bucket in grouped.items():
            markets.append(
                {
                    "key": key,
                    "accounts": bucket["accounts"],
                    "total": bucket["total"],
                }
            )
        markets.sort(key=lambda m: (-m["total"], m["key"]))

    if assignment_mode == "territory":
        use_proximity = bool(payload.get("use_geo_proximity", True))
        geo_weight = float(payload.get("geo_weight", 0.7))
        if geo_weight < 0:
            geo_weight = 0.0
        if geo_weight > 1:
            geo_weight = 1.0
        assignments, rep_stats, market_assignments = _assign_territory_mode(
            markets,
            reps,
            use_balance_by,
            resolved_geo_key_type,
            use_proximity=use_proximity,
            geo_weight=geo_weight,
        )
    else:
        assignments, rep_stats = _assign_named_account_mode(
            normalized_accounts, reps, use_balance_by
        )
        market_assignments = {}

    market_counts = _compute_market_counts(assignments)
    for rep_name, counts in market_counts.items():
        rep_stats[rep_name]["market_counts"] = counts

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

    suffix = payload.get("tab_suffix") or ""
    replace_existing = (payload.get("idempotency_action") or "") == "replace"

    summary_tab = f"Territory_Summary{suffix}"
    reps_tab = f"Rep_Assignments{suffix}"
    accounts_tab = f"Account_Assignments{suffix}"
    gaps_tab = f"Coverage_Gaps{suffix}"
    balance_tab = f"Balance_Report{suffix}"

    for tab in [summary_tab, reps_tab, accounts_tab, gaps_tab, balance_tab]:
        _ensure_sheet(sheets_service, spreadsheet_id, tab, replace=replace_existing)

    total_accounts = sum(stats["account_count"] for stats in rep_stats.values())
    total_arr = sum(stats["total_arr"] for stats in rep_stats.values())
    total_pipeline = sum(stats["total_pipeline"] for stats in rep_stats.values())

    account_headers = [
        "Account Id",
        "Account Name",
        "Market Key",
        "Assigned Rep",
        "Rep ID",
        "Assignment Reason",
    ]
    if arr_field:
        account_headers.append("ARR")
    if pipeline_field:
        account_headers.append("Pipeline")
    account_col_letters = {h: _col_letter(idx) for idx, h in enumerate(account_headers)}
    assigned_rep_col = account_col_letters["Assigned Rep"]
    arr_account_col = account_col_letters.get("ARR")
    pipeline_account_col = account_col_letters.get("Pipeline")

    summary_headers = ["Rep Name", "Rep ID", "Markets Assigned", "Account Count"]
    if arr_field:
        summary_headers.append("Total ARR")
        summary_headers.append("Avg Account Size")
    if pipeline_field:
        summary_headers.append("Total Pipeline")
    summary_headers.append("% of Total")

    summary_rows = [summary_headers]
    summary_col_letters = {h: _col_letter(idx) for idx, h in enumerate(summary_headers)}
    summary_data_start = 2
    summary_last_row = 1 + len(rep_stats)
    summary_load_header = (
        "Account Count"
        if use_balance_by == "count"
        else "Total ARR"
        if use_balance_by == "arr"
        else "Total Pipeline"
    )
    summary_load_col = summary_col_letters[summary_load_header]
    summary_account_col = summary_col_letters["Account Count"]
    summary_arr_col = summary_col_letters.get("Total ARR")
    for rep_name in sorted(rep_stats.keys()):
        stats = rep_stats[rep_name]
        markets_list = ", ".join(sorted(m for m in stats["markets"] if m))
        row_num = len(summary_rows) + 1
        account_count_formula = (
            f"=COUNTIF({_quote_tab(accounts_tab)}!${assigned_rep_col}:${assigned_rep_col},A{row_num})"
        )
        row = [
            rep_name,
            stats.get("rep_id", ""),
            markets_list,
            account_count_formula,
        ]
        if arr_field:
            total_arr_formula = (
                f"=SUMIF({_quote_tab(accounts_tab)}!${assigned_rep_col}:${assigned_rep_col},A{row_num},"
                f"{_quote_tab(accounts_tab)}!${arr_account_col}:${arr_account_col})"
            )
            row.append(total_arr_formula)
            avg_account_formula = f"=IFERROR({summary_arr_col}{row_num}/{summary_account_col}{row_num},0)"
            row.append(avg_account_formula)
        if pipeline_field:
            total_pipeline_formula = (
                f"=SUMIF({_quote_tab(accounts_tab)}!${assigned_rep_col}:${assigned_rep_col},A{row_num},"
                f"{_quote_tab(accounts_tab)}!${pipeline_account_col}:${pipeline_account_col})"
            )
            row.append(total_pipeline_formula)
        pct_formula = (
            f"=IFERROR({summary_load_col}{row_num}/SUM({summary_load_col}${summary_data_start}:"
            f"{summary_load_col}${summary_last_row})*100,0)"
        )
        row.append(pct_formula)
        summary_rows.append(row)

    if rep_stats:
        total_row_num = len(summary_rows) + 1
        totals_row = ["TOTAL", "", ""]
        totals_row.append(
            f"=SUM({summary_account_col}${summary_data_start}:{summary_account_col}${summary_last_row})"
        )
        if arr_field:
            totals_row.append(
                f"=SUM({summary_arr_col}${summary_data_start}:{summary_arr_col}${summary_last_row})"
            )
            totals_row.append(
                f"=IFERROR({summary_arr_col}{total_row_num}/{summary_account_col}{total_row_num},0)"
            )
        if pipeline_field:
            summary_pipeline_col = summary_col_letters["Total Pipeline"]
            totals_row.append(
                f"=SUM({summary_pipeline_col}${summary_data_start}:{summary_pipeline_col}${summary_last_row})"
            )
        totals_row.append(
            f"=IFERROR({summary_load_col}{total_row_num}/SUM({summary_load_col}${summary_data_start}:"
            f"{summary_load_col}${summary_last_row})*100,0)"
        )
        summary_rows.append(totals_row)

    reps_headers = ["Rep Name", "Rep ID", "Seed Region", "Accounts"]
    if arr_field:
        reps_headers.append("Total ARR")
    if pipeline_field:
        reps_headers.append("Total Pipeline")
    reps_headers.extend(["Top Markets", "Coverage %"])

    reps_rows = [reps_headers]
    reps_col_letters = {h: _col_letter(idx) for idx, h in enumerate(reps_headers)}
    reps_data_start = 2
    reps_last_row = 1 + len(rep_stats)
    reps_load_header = (
        "Accounts"
        if use_balance_by == "count"
        else "Total ARR"
        if use_balance_by == "arr"
        else "Total Pipeline"
    )
    reps_load_col = reps_col_letters[reps_load_header]
    for rep_name in sorted(rep_stats.keys()):
        stats = rep_stats[rep_name]
        top_markets = _build_top_markets(stats["market_counts"], 3)
        row_num = len(reps_rows) + 1
        accounts_formula = (
            f"=COUNTIF({_quote_tab(accounts_tab)}!${assigned_rep_col}:${assigned_rep_col},A{row_num})"
        )
        row = [
            rep_name,
            stats.get("rep_id", ""),
            stats.get("seed_region", ""),
            accounts_formula,
        ]
        if arr_field:
            total_arr_formula = (
                f"=SUMIF({_quote_tab(accounts_tab)}!${assigned_rep_col}:${assigned_rep_col},A{row_num},"
                f"{_quote_tab(accounts_tab)}!${arr_account_col}:${arr_account_col})"
            )
            row.append(total_arr_formula)
        if pipeline_field:
            total_pipeline_formula = (
                f"=SUMIF({_quote_tab(accounts_tab)}!${assigned_rep_col}:${assigned_rep_col},A{row_num},"
                f"{_quote_tab(accounts_tab)}!${pipeline_account_col}:${pipeline_account_col})"
            )
            row.append(total_pipeline_formula)
        coverage_formula = (
            f"=IFERROR({reps_load_col}{row_num}/SUM({reps_load_col}${reps_data_start}:{reps_load_col}${reps_last_row})*100,0)"
        )
        row.extend([top_markets, coverage_formula])
        reps_rows.append(row)

    account_rows = [account_headers]
    rep_id_lookup = {rep["name"]: rep.get("rep_id", "") for rep in reps}
    for assignment in assignments:
        account = assignment["account"]
        rep_name = assignment["rep_name"]
        row = [
            account.get("id", ""),
            account.get("name", ""),
            account.get("geo_key", ""),
            rep_name,
            rep_id_lookup.get(rep_name, ""),
            assignment.get("reason", ""),
        ]
        if arr_field:
            row.append(round(account.get("arr_value", 0.0), 2))
        if pipeline_field:
            row.append(round(account.get("pipeline_value", 0.0), 2))
        account_rows.append(row)

    gaps_rows = [
        ["Market Key", "Account Count", "Total Value", "Nearest Rep", "Suggested Action", "Priority"]
    ]
    if assignment_mode == "territory":
        has_seeded_rep = any(rep.get("seed_region") for rep in reps)
        for market in markets:
            key = market["key"]
            assignment = market_assignments.get(key, {})
            reason = assignment.get("reason", "")
            is_gap = key == UNKNOWN_MARKET or (has_seeded_rep and reason == "load_balance")
            if not is_gap:
                continue
            row_num = len(gaps_rows) + 1
            rep_name = assignment.get("rep_name", "")
            suggested = "Populate geo fields" if key == UNKNOWN_MARKET else f"Review coverage for {rep_name}"
            priority_formula = (
                f"=IF({_sheet_cell(balance_tab, '$G$4')}>{_sheet_cell(balance_tab, '$C$2')},\"High\",\"Medium\")"
            )
            gaps_rows.append(
                [
                    key,
                    len(market["accounts"]),
                    round(market["total"], 2),
                    rep_name,
                    suggested,
                    priority_formula,
                ]
            )
    else:
        gaps_rows.append(["Not applicable in named account mode.", "", "", "", "", ""])

    metric_label = "Accounts"
    if use_balance_by == "arr":
        metric_label = "ARR"
    elif use_balance_by == "pipeline":
        metric_label = "Pipeline"

    balance_headers = [
        "Metric",
        "Min",
        "Max",
        "Avg",
        "Std Dev",
        "Range",
        "CV",
        "Variance %",
        "Fairness Score",
        "Balance Status",
    ]
    balance_rows = [
        ["Parameters", "Target Balance (CV)", "Imbalance Threshold", "", "", "", "", "", "", ""],
        ["", 0.15, 0.25, "", "", "", "", "", "", ""],
        balance_headers,
    ]
    balance_col_letters = {h: _col_letter(idx) for idx, h in enumerate(balance_headers)}
    balance_row_num = 4
    summary_load_range = (
        f"{_quote_tab(summary_tab)}!{summary_load_col}${summary_data_start}:{summary_load_col}${summary_last_row}"
    )
    min_formula = f"=IFERROR(MIN({summary_load_range}),0)"
    max_formula = f"=IFERROR(MAX({summary_load_range}),0)"
    avg_formula = f"=IFERROR(AVERAGE({summary_load_range}),0)"
    std_formula = f"=IFERROR(STDEV({summary_load_range}),0)"
    range_formula = (
        f"=IFERROR({balance_col_letters['Max']}{balance_row_num}-{balance_col_letters['Min']}{balance_row_num},0)"
    )
    cv_formula = (
        f"=IFERROR({balance_col_letters['Std Dev']}{balance_row_num}/{balance_col_letters['Avg']}{balance_row_num},0)"
    )
    variance_formula = (
        f"=IFERROR({balance_col_letters['Range']}{balance_row_num}/{balance_col_letters['Avg']}{balance_row_num}*100,0)"
    )
    fairness_formula = (
        f"=IFERROR((1-{balance_col_letters['CV']}{balance_row_num})*100,100)"
    )
    balance_status_formula = (
        f"=IF({balance_col_letters['CV']}{balance_row_num}<={_sheet_cell(balance_tab, '$B$2')},\"Balanced\","
        f"IF({balance_col_letters['CV']}{balance_row_num}<={_sheet_cell(balance_tab, '$C$2')},\"Acceptable\",\"Imbalanced\"))"
    )
    balance_rows.append(
        [
            metric_label,
            min_formula,
            max_formula,
            avg_formula,
            std_formula,
            range_formula,
            cv_formula,
            variance_formula,
            fairness_formula,
            balance_status_formula,
        ]
    )

    _write_rows(sheets_service, spreadsheet_id, summary_tab, summary_rows)
    _write_rows(sheets_service, spreadsheet_id, reps_tab, reps_rows)
    _write_rows(sheets_service, spreadsheet_id, accounts_tab, account_rows)
    _write_rows(sheets_service, spreadsheet_id, gaps_tab, gaps_rows)
    _write_rows(sheets_service, spreadsheet_id, balance_tab, balance_rows)

    validation_errors: List[str] = []
    validation_errors.extend(warnings)
    if not normalized_accounts:
        validation_errors.append("No prospect accounts returned for the selected filters.")

    validation = {
        "status": "issues" if validation_errors else "ok",
        "errors": validation_errors,
    }

    summary = {
        "rep_count": len(reps),
        "account_count": len(normalized_accounts),
        "assignment_mode": assignment_mode,
        "balance_by": use_balance_by,
        "geo_key_type": resolved_geo_key_type,
        "auto_geo": auto_geo,
        "total_accounts": total_accounts,
        "total_arr": total_arr,
        "total_pipeline": total_pipeline,
    }

    return {
        "spreadsheet_id": spreadsheet_id,
        "url": f"https://docs.google.com/spreadsheets/d/{spreadsheet_id}",
        "tabs_created": [
            summary_tab,
            reps_tab,
            accounts_tab,
            gaps_tab,
            balance_tab,
        ],
        "created": created,
        "validation": validation,
        "summary": summary,
    }
