"""
Recipe: enrich a CSV with Zendesk organization ticket stats.
"""

from __future__ import annotations

import csv
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple

from g_gremlin import zendesk as zendesk_helpers


@dataclass
class OrgStats:
    total_tickets: int
    open_tickets: int
    last_ticket_at: str
    org_created_at: str


@dataclass
class ZendeskOrgStatsResult:
    orgs_requested: int
    orgs_found: int
    rows_written: int
    warnings: List[str]


def zendesk_org_stats(
    csv_path: Path,
    org_column: str,
    total_tickets_col: str,
    open_tickets_col: str,
    last_ticket_col: str,
    org_created_col: str,
    output_path: Path,
    config: Optional[zendesk_helpers.ZendeskConfig] = None,
) -> ZendeskOrgStatsResult:
    """
    Enrich a CSV with Zendesk organization-level ticket stats.
    """
    rows, headers = _read_csv(csv_path)
    if org_column not in headers:
        raise ValueError(f"Organization column '{org_column}' not found in {csv_path}.")

    org_ids: Dict[str, str] = {}
    for row in rows:
        raw = (row.get(org_column) or "").strip()
        extracted = zendesk_helpers.extract_zendesk_id(raw)
        if not extracted:
            continue
        if extracted not in org_ids:
            org_ids[extracted] = raw

    stats_by_org: Dict[str, OrgStats] = {}
    warnings: List[str] = []
    orgs_found = 0

    for org_id in org_ids:
        org, org_warning = _fetch_org(org_id, config=config)
        if org_warning:
            warnings.append(org_warning)
        if org is None:
            stats_by_org[org_id] = OrgStats(0, 0, "", "")
            continue

        orgs_found += 1
        tickets, ticket_warning = _fetch_org_tickets(org_id, config=config)
        if ticket_warning:
            warnings.append(ticket_warning)
        total, open_count, last_ticket_at = _compute_ticket_stats(tickets)
        org_created_at = (org.get("created_at") or "").strip()
        stats_by_org[org_id] = OrgStats(
            total_tickets=total,
            open_tickets=open_count,
            last_ticket_at=last_ticket_at,
            org_created_at=org_created_at,
        )

    output_headers = list(headers)
    for col in (total_tickets_col, open_tickets_col, last_ticket_col, org_created_col):
        if col not in output_headers:
            output_headers.append(col)

    output_path.parent.mkdir(parents=True, exist_ok=True)
    with output_path.open("w", newline="", encoding="utf-8") as handle:
        writer = csv.DictWriter(handle, fieldnames=output_headers)
        writer.writeheader()
        for row in rows:
            output_row = dict(row)
            raw = (row.get(org_column) or "").strip()
            org_id = zendesk_helpers.extract_zendesk_id(raw)
            stats = stats_by_org.get(org_id) if org_id else None
            if stats:
                output_row[total_tickets_col] = str(stats.total_tickets)
                output_row[open_tickets_col] = str(stats.open_tickets)
                output_row[last_ticket_col] = stats.last_ticket_at
                output_row[org_created_col] = stats.org_created_at
            else:
                output_row[total_tickets_col] = "0"
                output_row[open_tickets_col] = "0"
                output_row[last_ticket_col] = ""
                output_row[org_created_col] = ""
            writer.writerow(output_row)

    return ZendeskOrgStatsResult(
        orgs_requested=len(org_ids),
        orgs_found=orgs_found,
        rows_written=len(rows),
        warnings=warnings,
    )


def _read_csv(path: Path) -> Tuple[List[Dict[str, str]], List[str]]:
    with path.open("r", newline="", encoding="utf-8-sig") as handle:
        reader = csv.DictReader(handle)
        if reader.fieldnames is None:
            raise ValueError(f"CSV {path} is missing a header row.")
        rows = [dict(row) for row in reader]
        return rows, list(reader.fieldnames)


def _fetch_org(
    org_id: str, config: Optional[zendesk_helpers.ZendeskConfig] = None
) -> Tuple[Optional[Dict[str, object]], Optional[str]]:
    """
    Fetch a Zendesk organization by ID. Returns (org or None, warning or None).
    """
    try:
        data = zendesk_helpers.run_zendesk_get(f"organizations/{org_id}.json", config=config)
    except zendesk_helpers.ZendeskError as exc:
        msg = str(exc)
        if "404" in msg:
            return None, f"Organization id {org_id} not found (404)."
        return None, f"Error fetching organization {org_id}: {exc}"

    org = data.get("organization") if isinstance(data, dict) else None
    if not org:
        return None, f"Organization payload missing for id {org_id}."
    return org, None


def _fetch_org_tickets(
    org_id: str, config: Optional[zendesk_helpers.ZendeskConfig] = None
) -> Tuple[List[Dict[str, object]], Optional[str]]:
    """
    Fetch tickets for a Zendesk organization, following pagination.
    """
    tickets: List[Dict[str, object]] = []
    next_path: Optional[str] = f"organizations/{org_id}/tickets.json"
    page_count = 0
    max_pages = 5
    warning = None

    while next_path:
        page_count += 1
        if page_count > max_pages:
            warning = (
                f"Reached pagination limit for org id={org_id}; some tickets may be omitted."
            )
            break
        try:
            data = zendesk_helpers.run_zendesk_get(next_path, config=config)
        except zendesk_helpers.ZendeskError as exc:
            return tickets, f"Error fetching tickets for org {org_id}: {exc}"

        page_tickets = data.get("tickets") if isinstance(data, dict) else None
        if page_tickets:
            tickets.extend(page_tickets)
        next_page_raw = data.get("next_page") if isinstance(data, dict) else None
        next_path = next_page_raw or None

    return tickets, warning


def _compute_ticket_stats(tickets: List[Dict[str, object]]) -> Tuple[int, int, str]:
    open_statuses = {"new", "open", "pending"}
    total = 0
    open_count = 0
    latest: Optional[datetime] = None

    for ticket in tickets:
        total += 1
        status = (ticket.get("status") or "").strip().lower()
        if status in open_statuses:
            open_count += 1

        created_raw = (ticket.get("created_at") or "").strip()
        created_dt = _parse_datetime(created_raw)
        if created_dt is not None and (latest is None or created_dt > latest):
            latest = created_dt

    last_ticket_at = latest.isoformat() if latest else ""
    return total, open_count, last_ticket_at


def _parse_datetime(value: str) -> Optional[datetime]:
    if not value:
        return None
    cleaned = value.replace("Z", "+00:00")
    try:
        return datetime.fromisoformat(cleaned)
    except ValueError:
        return None
