"""
Recipe: enrich a CSV with Zendesk ticket stats per user email.
"""

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 ZendeskUserStatsResult:
    emails_requested: int
    users_found: int
    rows_written: int
    warnings: List[str]


def zendesk_user_stats(
    csv_path: Path,
    email_column: str,
    tickets_count_col: str,
    open_tickets_col: str,
    last_ticket_col: str,
    output_path: Path,
    config: Optional[zendesk_helpers.ZendeskConfig] = None,
) -> ZendeskUserStatsResult:
    """
    Enrich a CSV with Zendesk ticket stats per user email.
    """
    rows, headers = _read_csv(csv_path)
    if email_column not in headers:
        raise ValueError(f"Email column '{email_column}' not found in {csv_path}.")

    email_keys: Dict[str, str] = {}
    for row in rows:
        cleaned = _clean_email(row.get(email_column, ""))
        if not cleaned:
            continue
        key = cleaned.lower()
        if key not in email_keys:
            email_keys[key] = cleaned

    stats_by_email: Dict[str, Dict[str, object]] = {}
    warnings: List[str] = []
    users_found = 0

    for canonical_email, raw_email in email_keys.items():
        user, user_warning = _find_user(raw_email, config=config)
        if user_warning:
            warnings.append(user_warning)
        if user is None:
            stats_by_email[canonical_email] = {
                "total_count": 0,
                "open_count": 0,
                "last_ticket_at": "",
            }
            continue

        users_found += 1
        tickets, ticket_warning = _fetch_tickets(user, config=config)
        if ticket_warning:
            warnings.append(ticket_warning)
        total_count, open_count, last_ticket_at = _compute_ticket_stats(tickets)
        stats_by_email[canonical_email] = {
            "total_count": total_count,
            "open_count": open_count,
            "last_ticket_at": last_ticket_at,
        }

    output_headers = list(headers)
    for col in (tickets_count_col, open_tickets_col, last_ticket_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)
            cleaned = _clean_email(row.get(email_column, ""))
            if not cleaned:
                output_row[tickets_count_col] = "0"
                output_row[open_tickets_col] = "0"
                output_row[last_ticket_col] = ""
            else:
                key = cleaned.lower()
                stats = stats_by_email.get(
                    key,
                    {"total_count": 0, "open_count": 0, "last_ticket_at": ""},
                )
                output_row[tickets_count_col] = str(stats.get("total_count", 0))
                output_row[open_tickets_col] = str(stats.get("open_count", 0))
                output_row[last_ticket_col] = str(stats.get("last_ticket_at", ""))
            writer.writerow(output_row)

    return ZendeskUserStatsResult(
        emails_requested=len(email_keys),
        users_found=users_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 _clean_email(value: str) -> str:
    return (value or "").strip()


def _find_user(
    email: str, config: Optional[zendesk_helpers.ZendeskConfig] = None
) -> Tuple[Optional[Dict[str, object]], Optional[str]]:
    """
    Find the Zendesk user for the given email. Returns (user_dict or None, warning or None).
    """
    data = zendesk_helpers.run_zendesk_get(
        "users/search.json", params={"query": email}, config=config
    )
    users = data.get("users") if isinstance(data, dict) else None
    if not users:
        return None, None

    exact = None
    for user in users:
        candidate_email = (user.get("email") or "").strip().lower()
        if candidate_email == email.strip().lower():
            exact = user
            break

    chosen = exact or users[0]
    warning = None
    if len(users) > 1 and exact is None:
        warning = f"Multiple Zendesk users found for email '{email}'. Using first result (id={chosen.get('id')})."
    return chosen, warning


def _fetch_tickets(
    user: Dict[str, object], config: Optional[zendesk_helpers.ZendeskConfig] = None
) -> Tuple[List[Dict[str, object]], Optional[str]]:
    """
    Fetch all requested tickets for a user, following pagination.
    """
    user_id = user.get("id")
    if user_id is None:
        return [], "User record missing id; treating as no tickets."

    tickets: List[Dict[str, object]] = []
    next_path: Optional[str] = f"users/{user_id}/tickets/requested.json"
    page_count = 0
    max_pages = 50
    warning = None

    while next_path:
        page_count += 1
        if page_count > max_pages:
            warning = (
                f"Reached pagination limit for user id={user_id}; "
                "some tickets may be omitted."
            )
            break
        data = zendesk_helpers.run_zendesk_get(next_path, config=config)
        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]:
    """
    Compute total count, open-like count, and latest created_at timestamp.
    """
    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
    # Zendesk returns ISO8601 strings like "2020-01-01T12:00:00Z"
    cleaned = value.replace("Z", "+00:00")
    try:
        return datetime.fromisoformat(cleaned)
    except ValueError:
        return None
