"""
CRM Append workflow endpoints (Sheets → Dashboard hand-off).
"""

from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, ConfigDict, Field
from sqlalchemy.ext.asyncio import AsyncSession

from ... import settings as app_settings
from ...auth import get_current_account, get_current_user
from ...db import get_session
from ...models import Job, JobStatus, JobType
from ...services.crm_append_sessions import (
    MAX_SESSION_ROWS,
    session_store,
)

log = logging.getLogger("fmatch.api.crm_append")

router = APIRouter(prefix="/api/v2/crm-append", tags=["crm-append"])


# ---------------------------------------------------------------------------
# Pydantic models
# ---------------------------------------------------------------------------


class SheetMetadata(BaseModel):
    id: str = Field(..., description="Spreadsheet ID")
    name: str = Field(..., description="Worksheet name")
    range: str = Field(
        ..., alias="range_a1", description="Selected range in A1 notation"
    )
    url: Optional[str] = None

    model_config = ConfigDict(populate_by_name=True)


class CreateSessionRequest(BaseModel):
    sheet: SheetMetadata
    headers: List[str]
    rows: List[List[Any]] = Field(default_factory=list)
    metadata: Dict[str, Any] = Field(default_factory=dict)

    def validate_payload(self) -> None:
        if not self.headers:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="At least one header is required",
            )
        if not isinstance(self.rows, list):
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Rows must be a list of lists",
            )


class CreateSessionResponse(BaseModel):
    session_token: str
    expires_at: str
    row_count: int
    headers: List[str]


class SessionSnapshotRow(BaseModel):
    index: int
    data: Dict[str, Any]


class SessionDetailResponse(BaseModel):
    session_token: str
    created_at: str
    expires_at: str
    sheet: Dict[str, Any]
    snapshot: Dict[str, Any]
    metadata: Dict[str, Any] = Field(default_factory=dict)
    job: Optional[Dict[str, Any]] = None


class PreviewRow(BaseModel):
    row_index: int
    status: str
    suggested_action: str
    score: Optional[float] = None
    issues: List[str] = Field(default_factory=list)
    reason_chips: List[str] = Field(default_factory=list)
    data: Dict[str, Any]


class PreviewSummary(BaseModel):
    total_rows: int
    sampled_rows: int
    ready_rows: int
    needs_review_rows: int
    blocked_rows: int
    missing_domain: int
    duplicate_candidates: int


class PreviewResponse(BaseModel):
    generated_at: str
    session: Dict[str, Any]
    summary: PreviewSummary
    rows: List[PreviewRow]
    intelligence_config: Optional[Dict[str, Any]] = None


class ExecuteRequest(BaseModel):
    options: Dict[str, Any] = Field(default_factory=dict)


class ExecuteResponse(BaseModel):
    job_id: str
    status: str
    queued_at: str


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")


def _rows_as_dicts(headers: List[str], rows: List[List[Any]]) -> List[Dict[str, Any]]:
    safe_headers = list(headers)
    output: List[Dict[str, Any]] = []
    for row in rows:
        record: Dict[str, Any] = {}
        for idx, header in enumerate(safe_headers):
            record[header] = row[idx] if idx < len(row) else None
        output.append(record)
    return output


def _first_value(record: Dict[str, Any], candidates: List[str]) -> Optional[str]:
    for key in candidates:
        if key in record and record[key] not in (None, ""):
            value = record[key]
            return str(value)
    return None


def _normalize(value: Optional[str]) -> str:
    if value is None:
        return ""
    return str(value).strip().lower()


def _analyze_rows(headers: List[str], rows: List[List[Any]]) -> PreviewResponse:
    total_rows = len(rows)
    sample_rows = rows[:PREVIEW_SAMPLE_ROWS]
    rows_dict = _rows_as_dicts(headers, sample_rows)

    name_keys = [
        h
        for h in headers
        if h.lower() in {"company", "company name", "account", "account name", "name"}
    ]
    if not name_keys and headers:
        name_keys = [headers[0]]
    domain_keys = [
        h
        for h in headers
        if h.lower() in {"domain", "website", "company website", "site"}
    ]
    sfdc_keys = [
        h
        for h in headers
        if h.lower() in {"sfdc_id", "salesforce_id", "accountid", "account id", "sfid"}
    ]

    combo_counts: Dict[tuple[str, str], int] = {}
    for record in rows_dict:
        name_val = _normalize(_first_value(record, name_keys))
        domain_val = _normalize(_first_value(record, domain_keys))
        if name_val or domain_val:
            combo_counts[(name_val, domain_val)] = (
                combo_counts.get((name_val, domain_val), 0) + 1
            )

    preview_rows: List[PreviewRow] = []
    ready = review = blocked = missing_domain = duplicate_candidates = 0

    for idx, record in enumerate(rows_dict):
        issues: List[str] = []
        reason_chips: List[str] = []

        company_raw = _first_value(record, name_keys)
        domain_raw = _first_value(record, domain_keys)
        sfdc_id_raw = _first_value(record, sfdc_keys)

        if not company_raw or not str(company_raw).strip():
            issues.append("Missing company name")
        if not domain_raw or not str(domain_raw).strip():
            issues.append("Missing domain/website")
            missing_domain += 1
        combo_key = (_normalize(company_raw), _normalize(domain_raw))
        if combo_key in combo_counts and combo_counts[combo_key] > 1:
            duplicate_candidates += 1
            reason_chips.append("Duplicate in selection")

        if sfdc_id_raw and str(sfdc_id_raw).strip():
            reason_chips.append("Existing SFDC reference")

        # Determine status and action
        if issues:
            if len(issues) >= 2:
                status = "review"
                blocked += 1
            else:
                status = "needs_review"
                review += 1
        else:
            status = "ready"
            ready += 1

        if sfdc_id_raw:
            suggested_action = "link"
        elif issues:
            suggested_action = "review"
        else:
            suggested_action = "create"

        score = max(0.0, 1.0 - 0.25 * len(issues))
        row_preview = PreviewRow(
            row_index=idx,
            status=status,
            suggested_action=suggested_action,
            score=round(score, 2),
            issues=issues,
            reason_chips=reason_chips,
            data=record,
        )
        preview_rows.append(row_preview)

    summary = PreviewSummary(
        total_rows=total_rows,
        sampled_rows=len(rows_dict),
        ready_rows=ready,
        needs_review_rows=review,
        blocked_rows=blocked,
        missing_domain=missing_domain,
        duplicate_candidates=duplicate_candidates,
    )

    response = PreviewResponse(
        generated_at=_now_iso(),
        session={"row_count": len(rows_dict)},
        summary=summary,
        rows=preview_rows[:MAX_SESSION_ROWS],
        intelligence_config=None,
    )
    return response


# ---------------------------------------------------------------------------
# Routes
# ---------------------------------------------------------------------------


@router.post(
    "/sessions",
    response_model=CreateSessionResponse,
    status_code=status.HTTP_201_CREATED,
)
async def create_append_session(
    payload: CreateSessionRequest,
    account_id: str = Depends(get_current_account),
    user_id: str = Depends(get_current_user),
):
    """
    Accept a snapshot from the Google Sheets add-on and create an append session.
    """
    payload.validate_payload()

    try:
        token, record = await session_store.create_session(
            account_id=str(account_id),
            user_id=str(user_id),
            sheet_id=payload.sheet.id,
            sheet_name=payload.sheet.name,
            range_a1=payload.sheet.range,
            headers=payload.headers,
            rows=payload.rows,
            metadata=payload.metadata,
        )
    except ValueError as exc:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)
        ) from exc

    log.info(
        "Created CRM append session rows=%s sheet=%s/%s user=%s",
        record["sheet"]["row_count"],
        record["sheet"]["id"],
        record["sheet"]["name"],
        user_id,
    )

    return CreateSessionResponse(
        session_token=token,
        expires_at=record["expires_at"],
        row_count=record["sheet"]["row_count"],
        headers=record["sheet"]["headers"],
    )


def _require_session(
    token: str,
    record: Optional[Dict[str, Any]],
    *,
    account_id: str,
    user_id: str,
) -> Dict[str, Any]:
    if not record:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND, detail="Session not found or expired"
        )
    if str(record.get("token")) != str(token):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN, detail="Session token mismatch"
        )
    if str(record.get("account_id")) != str(account_id):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Session does not belong to this account",
        )
    if str(record.get("user_id")) != str(user_id):
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Session does not belong to this user",
        )
    return record


@router.get("/sessions/{token}", response_model=SessionDetailResponse)
async def get_session_details(
    token: str,
    account_id: str = Depends(get_current_account),
    user_id: str = Depends(get_current_user),
):
    record = await session_store.get_session(token)
    record = _require_session(
        token, record, account_id=str(account_id), user_id=str(user_id)
    )

    # Include snapshot rows as list-of-dicts for the dashboard
    headers = record["sheet"]["headers"]
    rows = record["snapshot"]["rows"]
    row_dicts = _rows_as_dicts(headers, rows)

    snapshot_payload = {
        "checksum": record["snapshot"]["checksum"],
        "row_count": len(rows),
        "headers": headers,
        "rows": row_dicts,
    }

    return SessionDetailResponse(
        session_token=token,
        created_at=record["created_at"],
        expires_at=record["expires_at"],
        sheet=record["sheet"],
        snapshot=snapshot_payload,
        metadata=record.get("metadata") or {},
        job=record.get("job"),
    )


@router.post("/sessions/{token}/preview", response_model=PreviewResponse)
async def generate_preview(
    token: str,
    account_id: str = Depends(get_current_account),
    user_id: str = Depends(get_current_user),
):
    record = await session_store.get_session(token)
    record = _require_session(
        token, record, account_id=str(account_id), user_id=str(user_id)
    )

    headers = record["sheet"]["headers"]
    rows = record["snapshot"]["rows"]

    preview = _analyze_rows(headers, rows)

    # Attempt to fetch a lightweight intelligence config (best effort)
    try:
        from ...api.intelligence_client import IntelligenceClient

        client = IntelligenceClient()
        sample_rows = _rows_as_dicts(headers, rows[: min(50, len(rows))])
        config = None
        if sample_rows:
            config = await client.auto_configure(
                sample_rows,
                sample_rows,
                goal="match",
                sample_rows=min(100, len(sample_rows)),
            )
        preview.intelligence_config = config or {}
    except Exception as intel_err:
        log.warning("CRM append preview intelligence fetch failed: %s", intel_err)
        preview.intelligence_config = {}

    await session_store.update_session(
        token,
        {
            "preview": {
                "generated_at": preview.generated_at,
                "summary": preview.summary.model_dump(),
                "intelligence_config": preview.intelligence_config,
            }
        },
    )

    return preview


@router.post(
    "/sessions/{token}/execute",
    response_model=ExecuteResponse,
    status_code=status.HTTP_202_ACCEPTED,
)
async def execute_append(
    token: str,
    body: ExecuteRequest,
    db: AsyncSession = Depends(get_session),
    account_id: str = Depends(get_current_account),
    user_id: str = Depends(get_current_user),
):
    record = await session_store.get_session(token)
    record = _require_session(
        token, record, account_id=str(account_id), user_id=str(user_id)
    )

    # Persist a queued job in the jobs table (worker implementation to follow).
    job = Job(
        account_id=str(account_id),
        status=JobStatus.QUEUED,
        job_type=JobType.MATCH,
        config={
            "workflow": "crm_append",
            "session_token": token,
            "options": body.options,
            "sheet": record["sheet"],
            "snapshot_checksum": record["snapshot"]["checksum"],
        },
    )

    db.add(job)
    await db.commit()
    await db.refresh(job)

    try:
        from anyio.to_thread import run_sync
        from rq import Queue

        async def _enqueue():
            def _do():
                Queue("default", connection=_rq_conn).enqueue(
                    "fmatch.saas.workers.crm_append_worker.run_crm_append_job",
                    str(job.id),
                    job_timeout=getattr(app_settings, "JOB_TIMEOUT_DEFAULT", 3600),
                )

            return await run_sync(_do)

        await _enqueue()
    except Exception as exc:
        log.error("Failed to enqueue CRM append job %s: %s", job.id, exc)
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail="Failed to enqueue CRM append job.",
        ) from exc

    await session_store.attach_job(token, str(job.id), status=job.status.value)

    return ExecuteResponse(
        job_id=str(job.id),
        status=job.status.value,
        queued_at=_now_iso(),
    )


from ...worker import conn as _rq_conn

PREVIEW_SAMPLE_ROWS = 100
