"""
Enrichment API endpoints for BYO Provider Locker.

Provides endpoints for:
- Validation (free, estimate credits)
- Live test (paid, up to 10 rows)
- Job execution (max 1000 rows)
- Job status polling
"""

from __future__ import annotations

from typing import Any, Dict, List, Optional
from uuid import UUID

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

from ...auth_core import require_org_context, OrgContext
from ...db import get_session
from ...services.enrichment_service import EnrichmentService
from ...services.apollo_oauth import get_apollo_integration
from ...services.secret_storage import get_saas_secret_backend
from ...services.usage_gateway import UsageGateway
from ...services.email_notifications import send_enrichment_complete_notification
from ...models import BYOEnrichmentJob, EnrichmentJobStatus, IntegrationStatus
from ... import settings

log = logging.getLogger(__name__)

router = APIRouter(prefix="/enrichment", tags=["Enrichment"])


# =============================================================================
# Request/Response Models
# =============================================================================


class InputMappingModel(BaseModel):
    """Column mapping for enrichment inputs."""
    domain: str = Field("domain", description="Column name containing domains")
    company_name: Optional[str] = Field(None, description="Column name for company names")
    firstname: Optional[str] = Field(None, description="Column name for contact first names")
    lastname: Optional[str] = Field(None, description="Column name for contact last names")
    email: Optional[str] = Field(None, description="Column name for contact emails")
    linkedin_url: Optional[str] = Field(None, description="Column name for LinkedIn URLs")


class ValidateRequest(BaseModel):
    """Request for validation (free)."""
    rows: List[Dict[str, Any]] = Field(..., description="Rows to validate")
    input_mapping: InputMappingModel = Field(default_factory=InputMappingModel)
    output_fields: List[str] = Field(
        default=["industry", "employee_count"],
        description="Fields to enrich"
    )
    provider: str = Field(
        default="foundrygraph",
        description="Selected provider for run policy evaluation",
    )
    providers: Optional[List[str]] = Field(
        default=None,
        description="Deprecated. Single-provider only; first value is used if set.",
    )
    enrichment_type: str = Field("company", description="'company' or 'contact'")
    run_policy: str = Field(
        default="best_effort",
        description="Run policy: 'best_effort' or 'minimum_provider'",
    )


class TestRequest(BaseModel):
    """Request for live test (paid, max 10 rows)."""
    rows: List[Dict[str, Any]] = Field(..., description="Rows to test")
    input_mapping: InputMappingModel = Field(default_factory=InputMappingModel)
    output_fields: List[str] = Field(default=["industry", "employee_count"])
    provider: str = Field(
        default="foundrygraph",
        description="Enrichment provider: 'foundrygraph', 'fullenrich', 'apollo', 'pdl', or 'amplemarket'",
    )
    providers: Optional[List[str]] = Field(
        default=None,
        description="Deprecated. Single-provider only; first value is used if set.",
    )
    enrichment_type: str = Field("company", description="'company' or 'contact'")
    run_policy: str = Field(
        default="best_effort",
        description="Run policy: 'best_effort' or 'minimum_provider'",
    )
    max_credits: Optional[int] = Field(
        default=None,
        ge=0,
        description="Optional credit guardrail. Rejects the request if estimated credits exceed this value.",
    )
    confirm: bool = Field(False, description="Must be true to execute test")


class RunRequest(BaseModel):
    """Request for full enrichment run."""
    rows: List[Dict[str, Any]] = Field(..., description="Rows to enrich (max 1000)")
    input_mapping: InputMappingModel = Field(default_factory=InputMappingModel)
    output_fields: List[str] = Field(default=["industry", "employee_count"])
    provider: str = Field(
        default="foundrygraph",
        description="Enrichment provider: 'foundrygraph', 'fullenrich', 'apollo', 'pdl', or 'amplemarket'",
    )
    providers: Optional[List[str]] = Field(
        default=None,
        description="Deprecated. Single-provider only; first value is used if set.",
    )
    enrichment_type: str = Field("company", description="'company' or 'contact'")
    run_policy: str = Field(
        default="best_effort",
        description="Run policy: 'best_effort' or 'minimum_provider'",
    )
    max_credits: Optional[int] = Field(
        default=None,
        ge=0,
        description="Optional credit guardrail. Rejects the request if estimated credits exceed this value.",
    )
    run_id: str = Field(..., description="Idempotency key")
    notify_email: Optional[str] = Field(None, description="Email address for job completion notification")
    spreadsheet_url: Optional[str] = Field(None, description="URL back to the spreadsheet with results")
    sheet_id: Optional[str] = Field(None, description="Spreadsheet ID for scheduled writeback")
    sheet_name: Optional[str] = Field(None, description="Sheet tab name for scheduled writeback")
    force_reenrich: Optional[bool] = Field(False, description="Overwrite existing output values")


class UpdateNotificationRequest(BaseModel):
    """Update notification settings for an enrichment job."""
    notify_email: Optional[str] = Field(None, description="Email address for job completion notification")
    spreadsheet_url: Optional[str] = Field(None, description="URL back to the spreadsheet with results")


# =============================================================================
# Helper Functions
# =============================================================================

VALID_PROVIDERS = {"foundrygraph", "fullenrich", "apollo", "pdl", "amplemarket"}
CONTACT_PROVIDERS = {"fullenrich", "apollo", "pdl", "amplemarket"}


async def require_byo_access(
    org: OrgContext,
    db: AsyncSession,
) -> None:
    """
    Verify the account has BYO enrichment enabled (Unleashed or add-on).
    """
    gateway = UsageGateway(db)
    capabilities = await gateway.get_capabilities(org.account_id)
    byo = capabilities.get("byo") or {}
    if not byo.get("enabled"):
        raise HTTPException(
            status_code=403,
            detail={
                "error": "TIER_REQUIRED",
                "message": "BYO enrichment requires Unleashed tier or BYO add-on",
                "current_tier": capabilities.get("tier", "free"),
                "required_tier": "unleashed_or_addon",
                "upgrade_url": "/pricing",
            },
        )


def _resolve_provider(request_body: BaseModel) -> str:
    provider = str(getattr(request_body, "provider", "") or "").strip().lower()
    if not provider:
        providers = getattr(request_body, "providers", None) or []
        if providers:
            provider = str(providers[0]).strip().lower()
    return provider or "foundrygraph"


def _enforce_credit_guardrail(
    *,
    max_credits: Optional[int],
    estimated_credits: int,
    operation: str,
) -> None:
    if max_credits is None:
        return
    max_credits_value = max(0, int(max_credits))
    estimated_credits_value = max(0, int(estimated_credits))
    if estimated_credits_value <= max_credits_value:
        return
    raise HTTPException(
        status_code=400,
        detail={
            "error": "CREDIT_GUARDRAIL_EXCEEDED",
            "message": (
                f"{operation} blocked: estimated {estimated_credits_value} credits exceeds "
                f"max_credits {max_credits_value}."
            ),
            "operation": operation,
            "estimated_credits": estimated_credits_value,
            "max_credits": max_credits_value,
        },
    )


async def validate_provider(
    provider: str,
    enrichment_type: str,
    account_id: str,
    db: AsyncSession,
) -> None:
    provider = (provider or "").strip().lower()
    enrichment_type = (enrichment_type or "company").lower()

    if provider not in VALID_PROVIDERS:
        raise HTTPException(400, f"Invalid provider: {provider}")

    if enrichment_type == "contact" and provider not in CONTACT_PROVIDERS:
        raise HTTPException(
            400,
            "Contact enrichment requires Apollo, FullEnrich, PDL, or Amplemarket.",
        )

    if provider == "apollo":
        integration = await get_apollo_integration(db, account_id)
        if not integration or integration.status != IntegrationStatus.CONNECTED:
            raise HTTPException(
                400,
                "Apollo not connected. Please connect in Provider Locker.",
            )
        secret_backend = get_saas_secret_backend()
        secret = await secret_backend.get_secret(
            db=db,
            integration="apollo",
            key="access_token",
            workspace_id=account_id,
        )
        if not secret:
            raise HTTPException(
                400,
                "Apollo tokens missing. Please reconnect.",
            )

    if provider == "fullenrich":
        secret_backend = get_saas_secret_backend()
        secret = await secret_backend.get_secret(
            db=db,
            integration="fullenrich",
            key="api_key",
            workspace_id=account_id,
        )
        if not secret:
            raise HTTPException(
                400,
                "FullEnrich not connected. Please add your API key.",
            )
    if provider == "pdl":
        secret_backend = get_saas_secret_backend()
        secret = await secret_backend.get_secret(
            db=db,
            integration="pdl",
            key="api_key",
            workspace_id=account_id,
        )
        if not secret:
            raise HTTPException(
                400,
                "PDL not connected. Please add your API key.",
            )
    if provider == "amplemarket":
        secret_backend = get_saas_secret_backend()
        secret = await secret_backend.get_secret(
            db=db,
            integration="amplemarket",
            key="api_key",
            workspace_id=account_id,
        )
        if not secret:
            raise HTTPException(
                400,
                "Amplemarket not connected. Please add your API key.",
            )


# =============================================================================
# API Endpoints
# =============================================================================


@router.post("/validate")
async def validate_enrichment(
    request_body: ValidateRequest,
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    """
    Validate enrichment inputs (free operation).

    Checks inputs, estimates credits, and shows provider status.
    No credits are consumed.

    Request body:
    - rows: Input data rows
    - input_mapping: Column mapping (domain column name)
    - output_fields: Desired output fields

    Returns:
    - rows_with_valid_inputs: Count of rows with valid domains
    - rows_skipped: Count of rows with missing/invalid domains
    - estimated_credits: Credits that would be consumed
    - providers_available: List of available providers
    """
    await require_byo_access(org, db)

    service = EnrichmentService(db, org.account_id)

    try:
        provider = _resolve_provider(request_body)
        result = await service.validate_inputs(
            rows=request_body.rows,
            input_mapping=request_body.input_mapping.model_dump(),
            output_fields=request_body.output_fields,
            enrichment_type=request_body.enrichment_type,
            provider=provider,
            run_policy=request_body.run_policy,
        )

        return {
            "ok": True,
            "data": result.to_dict(),
        }

    except Exception as e:
        log.error(f"Validation error: {e}", exc_info=True)
        raise HTTPException(
            status_code=500,
            detail={"error": "VALIDATION_ERROR", "message": str(e)},
        )


@router.post("/test")
async def test_enrichment(
    request_body: TestRequest,
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    """
    Run a live test enrichment on up to 10 rows.

    This CONSUMES CREDITS! Requires confirm=true.

    Request body:
    - rows: Input rows (first 10 will be used)
    - input_mapping: Column mapping
    - output_fields: Desired output fields
    - provider: Enrichment provider
    - confirm: Must be true to execute

    Returns:
    - results: Enriched row data
    - credits_consumed: Credits used
    - rows_enriched: Count of successfully enriched rows
    - rows_skipped: Count of skipped rows
    """
    await require_byo_access(org, db)

    if not request_body.confirm:
        raise HTTPException(
            status_code=400,
            detail={
                "error": "CONFIRMATION_REQUIRED",
                "message": "Set confirm=true to execute live test. This will consume credits.",
            },
        )

    service = EnrichmentService(db, org.account_id)
    provider = _resolve_provider(request_body)
    await validate_provider(
        provider=provider,
        enrichment_type=request_body.enrichment_type,
        account_id=org.account_id,
        db=db,
    )
    validation = await service.validate_inputs(
        rows=request_body.rows,
        input_mapping=request_body.input_mapping.model_dump(),
        output_fields=request_body.output_fields,
        enrichment_type=request_body.enrichment_type,
        provider=provider,
        run_policy=request_body.run_policy,
    )
    if validation.errors:
        raise HTTPException(
            status_code=400,
            detail={
                "error": "VALIDATION_ERROR",
                "message": validation.errors[0],
                "details": validation.errors,
            },
        )
    test_rows = min(validation.rows_with_valid_inputs, len(request_body.rows), 10)
    _enforce_credit_guardrail(
        max_credits=request_body.max_credits,
        estimated_credits=test_rows,
        operation="test",
    )

    gateway = UsageGateway(db)
    consume = await gateway.check_and_consume(
        account_id=org.account_id,
        resource="byo_rows",
        amount=test_rows,
        source="byo_test",
    )
    if not consume.allowed:
        raise HTTPException(
            status_code=429,
            detail={
                "error": "BYO_LIMIT_EXCEEDED",
                "message": consume.message or "BYO daily limit reached.",
                "limit": consume.limit,
                "used": consume.used,
                "remaining": consume.remaining,
                "reset_at": consume.reset_at.isoformat() if consume.reset_at else None,
            },
        )

    try:
        result = await service.run_test(
            rows=request_body.rows,
            input_mapping=request_body.input_mapping.model_dump(),
            output_fields=request_body.output_fields,
            provider=provider,
            enrichment_type=request_body.enrichment_type,
            max_rows=10,
            run_policy=request_body.run_policy,
        )

        return {
            "ok": True,
            "data": result.to_dict(),
        }

    except ValueError as e:
        raise HTTPException(
            status_code=400,
            detail={"error": "VALIDATION_ERROR", "message": str(e)},
        )
    except Exception as e:
        log.error(f"Test enrichment error: {e}", exc_info=True)
        raise HTTPException(
            status_code=500,
            detail={"error": "ENRICHMENT_ERROR", "message": str(e)},
        )


@router.post("/run")
async def run_enrichment(
    request_body: RunRequest,
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    """
    Create an enrichment job.

    Maximum 1000 rows per request. Uses idempotency key (run_id) to
    prevent duplicate job submissions.

    Request body:
    - rows: Input rows (max 1000)
    - input_mapping: Column mapping
    - output_fields: Desired output fields
    - provider: Enrichment provider
    - enrichment_type: "company" or "contact"
    - run_id: Idempotency key

    Returns:
    - job_id: UUID of the created job
    - run_id: Echo of the idempotency key
    - status: Current job status
    - estimated_credits: Expected credit consumption
    - existing: True if returning existing job
    """
    await require_byo_access(org, db)

    # Check row limit
    max_rows = settings.BYO_ENRICHMENT_MAX_ROWS
    if len(request_body.rows) > max_rows:
        raise HTTPException(
            status_code=400,
            detail={
                "error": "ROW_LIMIT_EXCEEDED",
                "message": f"Row limit exceeded: max {max_rows} rows per request",
                "max_rows": max_rows,
                "received_rows": len(request_body.rows),
            },
        )

    service = EnrichmentService(db, org.account_id)
    provider = _resolve_provider(request_body)
    await validate_provider(
        provider=provider,
        enrichment_type=request_body.enrichment_type,
        account_id=org.account_id,
        db=db,
    )
    validation = await service.validate_inputs(
        rows=request_body.rows,
        input_mapping=request_body.input_mapping.model_dump(),
        output_fields=request_body.output_fields,
        enrichment_type=request_body.enrichment_type,
        provider=provider,
        run_policy=request_body.run_policy,
    )
    if validation.errors:
        raise HTTPException(
            status_code=400,
            detail={
                "error": "VALIDATION_ERROR",
                "message": validation.errors[0],
                "details": validation.errors,
            },
        )
    _enforce_credit_guardrail(
        max_credits=request_body.max_credits,
        estimated_credits=validation.estimated_credits,
        operation="run",
    )

    gateway = UsageGateway(db)
    consume = await gateway.check_and_consume(
        account_id=org.account_id,
        resource="byo_rows",
        amount=validation.estimated_credits,
        source="byo_run",
        idempotency_key=f"byo:{org.account_id}:{request_body.run_id}",
    )
    if not consume.allowed:
        raise HTTPException(
            status_code=429,
            detail={
                "error": "BYO_LIMIT_EXCEEDED",
                "message": consume.message or "BYO daily limit reached.",
                "limit": consume.limit,
                "used": consume.used,
                "remaining": consume.remaining,
                "reset_at": consume.reset_at.isoformat() if consume.reset_at else None,
            },
        )

    try:
        result = await service.create_job(
            rows=request_body.rows,
            input_mapping=request_body.input_mapping.model_dump(),
            output_fields=request_body.output_fields,
            provider=provider,
            enrichment_type=request_body.enrichment_type,
            run_id=request_body.run_id,
            run_policy=request_body.run_policy,
            created_by=org.email,
            notify_email=request_body.notify_email,
            spreadsheet_url=request_body.spreadsheet_url,
            sheet_id=request_body.sheet_id,
            sheet_name=request_body.sheet_name,
            force_reenrich=request_body.force_reenrich,
        )

        return {
            "ok": True,
            "data": result.to_dict(),
        }

    except ValueError as e:
        raise HTTPException(
            status_code=400,
            detail={"error": "VALIDATION_ERROR", "message": str(e)},
        )
    except Exception as e:
        log.error(f"Job creation error: {e}", exc_info=True)
        raise HTTPException(
            status_code=500,
            detail={"error": "JOB_CREATION_ERROR", "message": str(e)},
        )


@router.patch("/run/{job_id}")
async def update_job_notification(
    job_id: UUID,
    request_body: UpdateNotificationRequest,
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    """
    Update notify_email or spreadsheet_url for an existing enrichment job.
    """
    await require_byo_access(org, db)

    job = await db.get(BYOEnrichmentJob, job_id)
    if not job or str(job.workspace_id) != str(org.account_id):
        raise HTTPException(
            status_code=404,
            detail={"error": "NOT_FOUND", "message": f"Job {job_id} not found"},
        )

    if request_body.notify_email is not None:
        job.notify_email = request_body.notify_email
    if request_body.spreadsheet_url is not None:
        job.spreadsheet_url = request_body.spreadsheet_url

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

    if (
        job.notify_email
        and job.status == EnrichmentJobStatus.COMPLETED.value
    ):
        summary = dict(job.summary or {})
        if not summary.get("notification_sent"):
            try:
                record_label = "contacts" if job.enrichment_type == "contact" else "companies"
                await send_enrichment_complete_notification(
                    user_email=job.notify_email,
                    job_name=f"Enrichment ({job.row_count} {record_label})",
                    job_id=str(job.id),
                    total_contacts=job.row_count,
                    enriched_count=job.rows_enriched,
                    not_found_count=max(
                        0,
                        job.rows_processed - job.rows_enriched - job.rows_skipped - job.rows_errored,
                    ),
                    error_count=job.rows_errored,
                    spreadsheet_url=job.spreadsheet_url,
                    record_label=record_label,
                )
                summary["notification_sent"] = True
                job.summary = summary
                await db.commit()
            except Exception as exc:
                log.warning(
                    "Failed to send notification for completed job %s: %s",
                    job.id,
                    exc,
                )

    return {
        "ok": True,
        "data": {
            "job_id": str(job.id),
            "notify_email": job.notify_email,
            "spreadsheet_url": job.spreadsheet_url,
        },
    }


@router.get("/run/{job_id}")
async def get_job_status(
    job_id: UUID,
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    """
    Get the status of an enrichment job.

    Poll this endpoint to track job progress. When status is "completed",
    results are available in the specified output_range (Google Sheets).

    Path parameters:
    - job_id: UUID of the job to check

    Returns:
    - job_id: Job UUID
    - status: queued, running, completed, or failed
    - progress: Percentage complete (0-100)
    - rows_processed: Rows processed so far
    - rows_total: Total rows in job
    - rows_enriched: Successfully enriched rows
    - rows_skipped: Skipped rows (invalid input)
    - rows_errored: Rows with errors
    - credits_consumed: Credits used so far
    - summary: Job summary (when completed)
    """
    await require_byo_access(org, db)

    service = EnrichmentService(db, org.account_id)
    status = await service.get_job_status(job_id)

    if status is None:
        raise HTTPException(
            status_code=404,
            detail={"error": "NOT_FOUND", "message": f"Job {job_id} not found"},
        )

    return {
        "ok": True,
        "data": status.to_dict(),
    }


@router.get("/jobs")
async def list_jobs(
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
    limit: int = 20,
    status: Optional[str] = None,
) -> Dict[str, Any]:
    """
    List enrichment jobs for the workspace.

    Query parameters:
    - limit: Max jobs to return (default 20)
    - status: Filter by status (queued, running, completed, failed)

    Returns:
    - jobs: List of job status objects
    """
    await require_byo_access(org, db)

    service = EnrichmentService(db, org.account_id)
    jobs = await service.list_jobs(limit=limit, status_filter=status)

    return {
        "ok": True,
        "data": {
            "jobs": [j.to_dict() for j in jobs],
        },
    }
