from __future__ import annotations

import json
import logging
import os
import time
from typing import Any, Dict, List, Literal, Optional
from uuid import uuid4

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

from .deps import require_api_key
from ...db import get_session
from ...api.progress import get_redis
from ... import settings
from ...services.transform_service import SUPPORTED_OPERATIONS, batch_transform
from ...services.gremlin_intent_router import ResolutionTarget

logger = logging.getLogger(__name__)

# Configurable limits
MAX_ROWS_PER_REQUEST = int(os.getenv("TRANSFORM_MAX_ROWS", "200"))

router = APIRouter(prefix="/api/v1", tags=["Transforms"])


class BatchRequest(BaseModel):
    op: str = Field(..., description="Operation, e.g., normalize_company")
    values: List[Any] = Field(..., description="Array of values to transform")
    params: Optional[Dict[str, Any]] = Field(default_factory=dict)
    resolution_target: Optional[Literal["smart", "salesforce", "foundrygraph"]] = Field(
        default=None,
        description="Resolution strategy for company enrichment: 'smart' (SFDC→FG), 'salesforce' (SFDC only), 'foundrygraph' (FG only)",
    )

    @validator("values")
    def validate_not_empty(cls, v):
        if len(v) == 0:
            raise ValueError("At least one value is required")
        return v


class BatchResponse(BaseModel):
    data: List[Any]
    meta: Dict[str, Any]


class ScheduleRequest(BaseModel):
    op: str = Field(..., description="Operation to schedule, e.g., enrich_company_name")
    values: List[Any] = Field(..., description="Array of values to transform")
    params: Optional[Dict[str, Any]] = Field(default_factory=dict)
    resolution_target: Optional[Literal["smart", "salesforce", "foundrygraph"]] = Field(
        default=None,
        description="Resolution strategy for company enrichment",
    )
    sheet_id: str = Field(..., description="Google Sheets spreadsheet ID")
    sheet_name: str = Field(..., description="Target sheet name")
    sheet_gid: Optional[int] = Field(default=None, description="Target sheet GID")
    row_base: int = Field(..., description="1-based start row for output range")
    row_count: int = Field(..., description="Total rows in output range")
    company_col: Optional[int] = Field(default=None, description="1-based company column index")
    domain_col: Optional[int] = Field(default=None, description="1-based domain column index")
    conversation_id: Optional[str] = Field(default=None, description="Gremlin conversation ID")
    only_fill_empty: Optional[bool] = Field(default=True)
    scope: Optional[str] = Field(default=None)
    step_id: Optional[str] = Field(default=None, description="Gremlin step id for UI tracking")

    @validator("values")
    def validate_schedule_values(cls, v):
        if not v:
            raise ValueError("At least one value is required")
        return v


@router.post("/transform/batch", response_model=BatchResponse)
async def transform_batch(
    body: BatchRequest,
    api_ctx=Depends(require_api_key),
    db: AsyncSession = Depends(get_session),
):
    """
    Transform a batch of values with guardrails and metrics.

    Limits:
    - Max rows per request: 200 (configurable via TRANSFORM_MAX_ROWS)
    - Automatic fallback to faster model on errors or slow responses
    - Returns heuristic results on JSON parse failures
    """
    # Check row limit and return 413 if exceeded
    if len(body.values) > MAX_ROWS_PER_REQUEST:
        raise HTTPException(
            status_code=413,
            detail=f"Too many rows: {len(body.values)} exceeds limit of {MAX_ROWS_PER_REQUEST}. "
            f"Please split into smaller batches.",
        )

    start_time = time.time()
    row_count = len(body.values)

    try:
        if body.op not in SUPPORTED_OPERATIONS:
            valid_ops = ", ".join(sorted(SUPPORTED_OPERATIONS))
            raise HTTPException(
                status_code=400,
                detail=f"Unknown operation: {body.op}. Valid operations: {valid_ops}",
            )
        # Execute transform with timeout handling done in service
        # Pass account_id and db for SFDC-aware operations like enrich_company_name
        # Parse resolution_target if provided
        resolution_target: Optional[ResolutionTarget] = None
        if body.resolution_target:
            try:
                resolution_target = ResolutionTarget(body.resolution_target)
            except ValueError:
                resolution_target = ResolutionTarget.SMART

        result = await batch_transform(
            body.op,
            body.values,
            body.params or {},
            account_id=api_ctx.account_id,
            db=db,
            resolution_target=resolution_target,
        )

        # Calculate metrics
        elapsed_ms = int((time.time() - start_time) * 1000)

        # Extract LLM metrics if available
        meta = result.get("meta", {})
        llm_metrics = meta.get("llm_metrics", {})

        # Log important metrics
        metrics = {
            "op": body.op,
            "rows": row_count,
            "latency_ms": elapsed_ms,
            "cache_hit_rate": meta.get("cache_hit_rate", 0),
            "fallback_used": llm_metrics.get("fallback_used", False),
            "model": llm_metrics.get("model"),
            "p95_latency": llm_metrics.get("latency_ms"),
        }

        # Log warning if fallback was needed
        if metrics["fallback_used"]:
            logger.warning(f"Transform used fallback model: {metrics}")
        elif elapsed_ms > 1000:
            logger.warning(f"Slow transform response: {metrics}")
        else:
            logger.info(f"Transform completed: {metrics}")

        # Add metrics to response meta
        if "meta" not in result:
            result["meta"] = {}
        result["meta"]["metrics"] = metrics

        return BatchResponse(**result)

    except ValueError as e:
        logger.error(f"Transform validation failed for {body.op}: {e}", exc_info=True)
        raise HTTPException(status_code=400, detail=str(e))
    except Exception as e:
        logger.error(f"Transform failed for {body.op}: {e}", exc_info=True)

        # Return partial results if possible
        if hasattr(e, "partial_results"):
            return BatchResponse(
                data=e.partial_results, meta={"error": str(e), "partial": True}
            )

        # Re-raise with clear message
        raise HTTPException(status_code=500, detail=f"Transform failed: {str(e)[:200]}")


@router.post("/transform/schedule")
async def transform_schedule(
    body: ScheduleRequest,
    api_ctx=Depends(require_api_key),
):
    """
    Schedule a transform for background processing and delivery via scheduled-results.
    """
    if body.op not in SUPPORTED_OPERATIONS:
        valid_ops = ", ".join(sorted(SUPPORTED_OPERATIONS))
        raise HTTPException(
            status_code=400,
            detail=f"Unknown operation: {body.op}. Valid operations: {valid_ops}",
        )

    if not body.sheet_id or not body.sheet_name:
        raise HTTPException(status_code=400, detail="sheet_id and sheet_name are required")

    redis_conn = await get_redis()
    if not redis_conn:
        raise HTTPException(status_code=503, detail="Redis not available")

    job_id = uuid4().hex
    ttl_seconds = int(getattr(settings, "BYO_ENRICHMENT_ROW_TTL_SECONDS", 86400))

    rows_key = f"scheduled_transform_rows:{job_id}"
    meta_key = f"scheduled_transform_meta:{job_id}"

    rows_payload = json.dumps(body.values, default=str)
    await redis_conn.setex(rows_key, ttl_seconds, rows_payload)

    meta = {
        "job_id": job_id,
        "op": body.op,
        "params": body.params or {},
        "resolution_target": body.resolution_target,
        "sheet_id": body.sheet_id,
        "sheet_name": body.sheet_name,
        "sheet_gid": body.sheet_gid,
        "row_base": body.row_base,
        "row_count": body.row_count,
        "company_col": body.company_col,
        "domain_col": body.domain_col,
        "conversation_id": body.conversation_id,
        "only_fill_empty": bool(body.only_fill_empty),
        "scope": body.scope,
        "step_id": body.step_id,
        "account_id": api_ctx.account_id,
        "submitted_at": time.time(),
    }
    await redis_conn.setex(meta_key, ttl_seconds, json.dumps(meta, default=str))

    try:
        from anyio.to_thread import run_sync
        from rq import Queue
        from ...worker import conn as _rq_conn

        async def _enqueue() -> None:
            def _do() -> None:
                Queue(settings.RQ_QUEUE_NAME, connection=_rq_conn).enqueue(
                    "fmatch.saas.worker.run_scheduled_transform_job",
                    job_id,
                    job_timeout=getattr(settings, "JOB_TIMEOUT_DEFAULT", 3600),
                )

            return await run_sync(_do)

        await _enqueue()
    except Exception as exc:
        logger.error("Failed to enqueue transform job %s: %s", job_id, exc)
        raise HTTPException(status_code=500, detail="Failed to enqueue transform job")

    return {"ok": True, "job_id": job_id}
