from __future__ import annotations

import json
import time
import uuid
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel, ConfigDict, Field, ValidationError, field_validator

from .deps import require_api_key
from ...db import get_session
from ...repos.salesforce_ai_reports_repo import SalesforceAIReportsRepo
from ...services.ai_rate_limiter import OrgLimiter
from ...services.salesforce_ai_reports import (
    PlannerUnavailable,
    PlanValidationError,
    SalesforceAIReportService,
    SalesforcePlan,
    PlanPatch,
    _ensure_patch_summary,
    hash_plan_for_storage,
)
from ...services.salesforce_gateway import SalesforceGateway
from ...services.telemetry import emit
from ...settings import (
    AI_REPORTS_ENABLED,
    AI_REPORTS_MAX_CONCURRENT_GLOBAL,
    AI_REPORTS_MAX_CONCURRENT_PER_ORG,
    AI_REPORTS_MAX_FIELDS,
    AI_REPORTS_MAX_OBJECTS,
    AI_REPORTS_PREVIEW_LIMIT,
)
from ...utils.salesforce_errors import normalize_sf_error


router = APIRouter(prefix="/salesforce/ai-reports", tags=["Salesforce AI Reports"])
service = SalesforceAIReportService()
limiter = OrgLimiter(
    AI_REPORTS_MAX_CONCURRENT_PER_ORG,
    AI_REPORTS_MAX_CONCURRENT_GLOBAL,
)


class ConversationTurn(BaseModel):
    model_config = ConfigDict(extra="forbid")

    role: str
    content: str

    @field_validator("role")
    @classmethod
    def _normalize_role(cls, value: str) -> str:
        if not value:
            return "user"
        return value.lower()


class PlanRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    prompt: str
    conversation: List[ConversationTurn] = Field(default_factory=list)
    prior_warnings: List[str] = Field(default_factory=list)
    clarifier_answers: Dict[str, str] = Field(
        default_factory=dict, alias="clarifierAnswers"
    )


class DiscoverRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    prompt: str = ""


class PatchRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    plan: Dict[str, Any]
    patch: Dict[str, Any]
    prompt: Optional[str] = None


class PreviewRequest(BaseModel):
    model_config = ConfigDict(extra="forbid", populate_by_name=True)

    plan: Dict[str, Any]
    tier_override: Optional[str] = Field(default=None, alias="tier")


class SaveReportRequest(BaseModel):
    model_config = ConfigDict(extra="forbid")

    name: str
    description: Optional[str] = None
    plan: Dict[str, Any]
    compiled_soql: str
    scopes: List[str] = Field(default_factory=list)
    schedule: Optional[Dict[str, Any]] = None


class ReportResponse(BaseModel):
    model_config = ConfigDict(extra="allow")

    id: str
    name: str
    description: Optional[str]
    primary_object: str
    plan: Dict[str, Any]
    compiled_soql: str
    created_at: str
    updated_at: str
    tier: Optional[str]
    plan_hash: str


async def _get_gateway(account_id: str, db_session) -> SalesforceGateway:
    return await SalesforceGateway.for_tenant(str(account_id), db_session)


@router.get("/config")
async def fetch_config():
    return {
        "enabled": AI_REPORTS_ENABLED,
        "previewLimit": AI_REPORTS_PREVIEW_LIMIT,
        "maxFields": AI_REPORTS_MAX_FIELDS,
        "maxObjects": AI_REPORTS_MAX_OBJECTS,
    }


@router.get("/schema")
async def fetch_schema_context(api_ctx=Depends(require_api_key)):
    account_id = api_ctx.account_id
    async for db in get_session():
        gateway = await _get_gateway(account_id, db)
        context = await service.build_schema_context(
            account_id=account_id,
            gateway=gateway,
            prompt="",
            db_session=db,
        )
        return {"ok": True, "data": context.to_payload()}


@router.get("/schema/peek")
async def peek_schema_field(
    object: str, field: str, limit: int = 3, api_ctx=Depends(require_api_key)
):
    if not object or not field:
        raise HTTPException(400, detail="object and field parameters are required")
    account_id = api_ctx.account_id
    async for db in get_session():
        gateway = await _get_gateway(account_id, db)
        try:
            payload = await service.peek_schema_field(
                account_id=account_id,
                gateway=gateway,
                object_name=object,
                field_name=field,
                sample_limit=limit,
            )
        except PlanValidationError as exc:
            raise HTTPException(400, detail=str(exc)) from exc
        return {"ok": True, "data": payload}


@router.post("/discover")
async def discover_context(
    body: DiscoverRequest, request: Request, api_ctx=Depends(require_api_key)
):
    account_id = api_ctx.account_id
    request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())
    start = time.perf_counter()
    async for db in get_session():
        gateway = await _get_gateway(account_id, db)
        research, schema_context = await service.discover(
            account_id=account_id,
            prompt=body.prompt,
            gateway=gateway,
            db_session=db,
            request_id=request_id,
        )
        ranked = getattr(research, "ranked_fields", None) or []
        suggested = getattr(research, "suggested_fields", None) or []
        clarifiers = getattr(research, "clarifiers", None) or []
        index_meta = getattr(research, "index_status", None)
        emit(
            "discover.ttfb_ms",
            request_id=request_id,
            org_id=account_id,
            ms=int((time.perf_counter() - start) * 1000),
            suggested=len(ranked) if ranked else len(suggested),
            clarifiers=len(clarifiers),
        )
        return {
            "ok": True,
            "discover": research.model_dump(by_alias=True),
            "schema": schema_context.to_payload(),
            "index_status": index_meta,
            "request_id": request_id,
        }


@router.post("/plan")
async def plan_report(
    body: PlanRequest, request: Request, api_ctx=Depends(require_api_key)
):
    account_id = api_ctx.account_id
    tier = getattr(api_ctx, "tier", None)
    request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())

    if body.clarifier_answers:
        for clarifier_id, choice in body.clarifier_answers.items():
            emit(
                "clarifier.picked",
                request_id=request_id,
                org_id=account_id,
                clarifier_id=clarifier_id,
                choice=choice,
            )

    async for db in get_session():
        gateway = await _get_gateway(account_id, db)
        try:
            schema_context = await service.build_schema_context(
                account_id=account_id,
                gateway=gateway,
                prompt=body.prompt,
                db_session=db,
            )
            artifacts = await service.plan_query(
                account_id=account_id,
                tier=tier,
                prompt=body.prompt,
                gateway=gateway,
                prior_warnings=body.prior_warnings,
                conversation=[turn.model_dump() for turn in body.conversation],
                schema_context=schema_context,
                clarifier_answers=body.clarifier_answers,
                db_session=db,
                request_id=request_id,
            )

            row_cap = service.tier_row_cap(tier)
            warnings = service.validate_plan(
                plan=artifacts.plan,
                schema_context=schema_context,
                row_cap=row_cap,
            )

            soql = service.compile_plan(artifacts.plan)
            plan_hash = hash_plan_for_storage(artifacts.plan)
            clarifiers_payload = [
                clarifier.model_dump(by_alias=True)
                for clarifier in artifacts.clarifiers
            ]
            alternatives_payload = [
                alt.model_dump(by_alias=True) for alt in artifacts.alternatives
            ]
            for alt in alternatives_payload:
                alt.setdefault("patchSummary", alt.get("patch", {}).get("summary"))
            discover_payload = (
                artifacts.research.model_dump(by_alias=True)
                if artifacts.research
                else None
            )

            return {
                "ok": True,
                "plan": artifacts.plan.model_dump(by_alias=True),
                "compiled_soql": soql,
                "trace": artifacts.trace,
                "clarifiers": clarifiers_payload,
                "risks": artifacts.risks,
                "alternatives": alternatives_payload,
                "discover": discover_payload,
                "warnings": warnings,
                "plan_hash": plan_hash,
                "preview_limit": AI_REPORTS_PREVIEW_LIMIT,
                "row_cap": row_cap,
                "request_id": request_id,
            }
        except PlannerUnavailable as exc:
            raise HTTPException(503, detail=str(exc)) from exc
        except PlanValidationError as exc:
            raise HTTPException(400, detail=str(exc)) from exc


@router.post("/patch")
async def patch_plan(body: PatchRequest, api_ctx=Depends(require_api_key)):
    account_id = api_ctx.account_id
    tier = getattr(api_ctx, "tier", None)

    try:
        base_plan = SalesforcePlan.model_validate(body.plan)
    except ValidationError as exc:
        raise HTTPException(400, detail=f"Invalid plan payload: {exc}") from exc

    try:
        plan_patch = PlanPatch.model_validate(body.patch)
    except ValidationError as exc:
        raise HTTPException(400, detail=f"Invalid patch payload: {exc}") from exc

    summary = _ensure_patch_summary(plan_patch).summary

    async for db in get_session():
        gateway = await _get_gateway(account_id, db)
        try:
            updated_plan = service.apply_patch(base_plan, plan_patch)
            schema_context = await service.build_schema_context(
                account_id=account_id,
                gateway=gateway,
                prompt=body.prompt
                or (updated_plan.objects[0] if updated_plan.objects else ""),
            )
            warnings = service.validate_plan(
                plan=updated_plan,
                schema_context=schema_context,
                row_cap=service.tier_row_cap(tier),
            )
        except PlanValidationError as exc:
            raise HTTPException(400, detail=str(exc)) from exc

        soql = service.compile_plan(updated_plan)
        plan_hash = hash_plan_for_storage(updated_plan)
        return {
            "ok": True,
            "plan": updated_plan.model_dump(by_alias=True),
            "compiled_soql": soql,
            "warnings": warnings,
            "patch_summary": summary,
            "plan_hash": plan_hash,
        }


@router.post("/preview")
async def preview_plan(
    body: PreviewRequest, request: Request, api_ctx=Depends(require_api_key)
):
    account_id = api_ctx.account_id
    tier = body.tier_override or getattr(api_ctx, "tier", None)
    request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())

    try:
        plan = SalesforcePlan.model_validate(body.plan)
    except ValidationError as exc:
        raise HTTPException(400, detail=f"Invalid plan payload: {exc}") from exc

    limiter.acquire(account_id)
    try:
        async for db in get_session():
            gateway = await _get_gateway(account_id, db)
            try:
                focus_prompt = plan.objects[0] if plan.objects else ""
                schema_context = await service.build_schema_context(
                    account_id=account_id,
                    gateway=gateway,
                    prompt=focus_prompt,
                    db_session=db,
                )
                warnings = service.validate_plan(
                    plan=plan,
                    schema_context=schema_context,
                    row_cap=service.tier_row_cap(tier),
                )
                preview = await service.preview_plan(
                    plan=plan,
                    gateway=gateway,
                    account_id=account_id,
                    tier=tier,
                    request_id=request_id,
                )
                return {
                    "ok": True,
                    "preview": preview,
                    "warnings": warnings,
                    "request_id": request_id,
                }
            except PlanValidationError as exc:
                raise HTTPException(400, detail=str(exc)) from exc
            except Exception as exc:
                payload = getattr(exc, "payload", None)
                if not payload and hasattr(exc, "invalid_fields"):
                    payload = {
                        "errorCode": "INSUFFICIENT_ACCESS",
                        "message": ", ".join(getattr(exc, "invalid_fields", [])),
                    }
                normalized = normalize_sf_error(
                    payload
                    or {"message": str(exc), "error": getattr(exc, "code", "UNKNOWN")}
                )
                status_code = 429 if normalized.kind == "rate_limit" else 400
                detail = normalized.model_dump()
                suggestions = service.suggest_autofix(
                    plan=plan, error_message=normalized.message
                )
                if suggestions:
                    autofix_payload = []
                    for suggestion in suggestions:
                        data = suggestion.model_dump(by_alias=True)
                        data.setdefault(
                            "patchSummary", data.get("patch", {}).get("summary")
                        )
                        autofix_payload.append(data)
                    detail["autofix"] = autofix_payload
                raise HTTPException(status_code=status_code, detail=detail) from exc
    finally:
        limiter.release(account_id)


@router.post("/reports")
async def save_report(body: SaveReportRequest, api_ctx=Depends(require_api_key)):
    account_id = api_ctx.account_id
    user_id = getattr(api_ctx, "api_key_prefix", "api")
    tier = getattr(api_ctx, "tier", None)

    try:
        plan = SalesforcePlan.model_validate(body.plan)
    except ValidationError as exc:
        raise HTTPException(400, detail=f"Invalid plan payload: {exc}") from exc

    if not body.name.strip():
        raise HTTPException(400, detail="Name is required")

    async for db in get_session():
        gateway = await _get_gateway(account_id, db)
        try:
            schema_context = await service.build_schema_context(
                account_id=account_id,
                gateway=gateway,
                prompt=plan.objects[0] if plan.objects else "",
                db_session=db,
            )
            service.validate_plan(
                plan=plan,
                schema_context=schema_context,
                row_cap=service.tier_row_cap(tier),
            )
        except PlanValidationError as exc:
            raise HTTPException(400, detail=str(exc)) from exc

        repo = SalesforceAIReportsRepo(db)
        plan_hash = hash_plan_for_storage(plan)
        record = await repo.create_report(
            account_id=account_id,
            user_id=user_id,
            name=body.name,
            description=body.description,
            primary_object=plan.objects[0] if plan.objects else "Lead",
            plan_json=plan.model_dump(by_alias=True),
            compiled_soql=body.compiled_soql,
            plan_hash=plan_hash,
            tier=tier,
            scopes=body.scopes,
            schedule=body.schedule,
        )
        return {
            "ok": True,
            "report": ReportResponse(
                id=record.id,
                name=record.name,
                description=record.description,
                primary_object=record.primary_object,
                plan=body.plan,
                compiled_soql=body.compiled_soql,
                created_at=record.created_at.isoformat(),
                updated_at=record.updated_at.isoformat(),
                tier=record.tier,
                plan_hash=plan_hash,
            ).model_dump(),
        }


@router.get("/reports")
async def list_reports(api_ctx=Depends(require_api_key)):
    account_id = api_ctx.account_id

    async for db in get_session():
        repo = SalesforceAIReportsRepo(db)
        rows = await repo.list_reports(account_id=account_id)
        out: List[Dict[str, Any]] = []
        for row in rows:
            try:
                plan_payload = json.loads(row.plan_json)
            except Exception:
                plan_payload = {}
            out.append(
                {
                    "id": row.id,
                    "name": row.name,
                    "description": row.description,
                    "primary_object": row.primary_object,
                    "plan": plan_payload,
                    "compiled_soql": row.compiled_soql,
                    "created_at": row.created_at.isoformat(),
                    "updated_at": row.updated_at.isoformat(),
                    "tier": row.tier,
                    "plan_hash": row.plan_hash,
                }
            )
        return {"ok": True, "reports": out}
