"""Scraper API endpoints for BYO Apify web scraping."""

from __future__ import annotations

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

from fastapi import APIRouter, Depends, Header, 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.scraper_service import ScraperService
from ...services.usage_gateway import UsageGateway
from ... import settings

log = logging.getLogger(__name__)

router = APIRouter(prefix="/scraper", tags=["Scraper"])


class ValidateRequest(BaseModel):
    rows: List[Dict[str, Any]] = Field(..., description="Rows to validate")
    actor_type: str = Field(..., description="Curated actor type")
    input_mapping: Dict[str, str] = Field(default_factory=dict)
    output_fields: List[str] = Field(default_factory=list)


class TestRequest(BaseModel):
    rows: List[Dict[str, Any]] = Field(..., description="Rows to test")
    actor_type: str = Field(..., description="Curated actor type")
    input_mapping: Dict[str, str] = Field(default_factory=dict)
    output_fields: List[str] = Field(default_factory=list)
    confirm: bool = Field(False, description="Must be true to execute test")


class RunRequest(BaseModel):
    rows: List[Dict[str, Any]] = Field(..., description="Rows to scrape")
    actor_type: str = Field(..., description="Curated actor type")
    input_mapping: Dict[str, str] = Field(default_factory=dict)
    output_fields: List[str] = Field(default_factory=list)
    run_id: Optional[str] = Field(None, description="Idempotency key")
    notify_email: Optional[str] = Field(None)
    sheet_id: Optional[str] = Field(None)
    sheet_name: Optional[str] = Field(None)


async def require_byo_access(org: OrgContext, db: AsyncSession) -> None:
    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 scraping requires Unleashed tier or BYO add-on",
                "current_tier": capabilities.get("tier", "free"),
                "required_tier": "unleashed_or_addon",
                "upgrade_url": "/pricing",
            },
        )


@router.get("/actors")
async def list_actors(
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    await require_byo_access(org, db)
    service = ScraperService(db, org.account_id)
    actors = await service.list_actors()
    return {"ok": True, "data": {"actors": actors}}


@router.post("/validate")
async def validate_scraper(
    request_body: ValidateRequest,
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    await require_byo_access(org, db)
    service = ScraperService(db, org.account_id)
    try:
        result = await service.validate_inputs(
            rows=request_body.rows,
            input_mapping=request_body.input_mapping,
            actor_type=request_body.actor_type,
        )
        return {"ok": True, "data": result.to_dict()}
    except Exception as exc:
        log.error("Scraper validation error: %s", exc, exc_info=True)
        raise HTTPException(
            status_code=500,
            detail={"error": "VALIDATION_ERROR", "message": str(exc)},
        )


@router.post("/test")
async def test_scraper(
    request_body: TestRequest,
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    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.",
            },
        )

    service = ScraperService(db, org.account_id)
    validation = await service.validate_inputs(
        rows=request_body.rows,
        input_mapping=request_body.input_mapping,
        actor_type=request_body.actor_type,
    )
    if not validation.byo_provider_connected:
        raise HTTPException(
            status_code=400,
            detail={
                "error": "APIFY_NOT_CONNECTED",
                "message": "Apify not connected. Please add your API token.",
            },
        )
    test_rows = min(validation.rows_with_valid_inputs, len(request_body.rows), 5)

    gateway = UsageGateway(db)
    consume = await gateway.check_and_consume(
        account_id=org.account_id,
        resource="byo_rows",
        amount=test_rows,
        source="byo_scraper_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,
            output_fields=request_body.output_fields,
            actor_type=request_body.actor_type,
            max_rows=5,
        )
        return {"ok": True, "data": result.to_dict()}
    except Exception as exc:
        log.error("Scraper test error: %s", exc, exc_info=True)
        raise HTTPException(
            status_code=500,
            detail={"error": "SCRAPER_TEST_ERROR", "message": str(exc)},
        )


@router.post("/run")
async def run_scraper(
    request_body: RunRequest,
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
    idempotency_key: Optional[str] = Header(default=None, alias="Idempotency-Key"),
) -> Dict[str, Any]:
    await require_byo_access(org, db)

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

    run_id = (idempotency_key or request_body.run_id or "").strip()
    if not run_id:
        run_id = uuid4().hex

    service = ScraperService(db, org.account_id)
    validation = await service.validate_inputs(
        rows=request_body.rows,
        input_mapping=request_body.input_mapping,
        actor_type=request_body.actor_type,
    )
    if not validation.byo_provider_connected:
        raise HTTPException(
            status_code=400,
            detail={
                "error": "APIFY_NOT_CONNECTED",
                "message": "Apify not connected. Please add your API token.",
            },
        )

    gateway = UsageGateway(db)
    consume = await gateway.check_and_consume(
        account_id=org.account_id,
        resource="byo_rows",
        amount=validation.estimated_credits,
        source="byo_scraper_run",
        idempotency_key=f"scraper:{org.account_id}:{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,
            actor_type=request_body.actor_type,
            input_mapping=request_body.input_mapping,
            output_fields=request_body.output_fields,
            run_id=run_id,
            created_by=org.email,
            notify_email=request_body.notify_email,
            sheet_id=request_body.sheet_id,
            sheet_name=request_body.sheet_name,
        )
        return {"ok": True, "data": result.to_dict()}
    except ValueError as exc:
        raise HTTPException(
            status_code=400,
            detail={"error": "VALIDATION_ERROR", "message": str(exc)},
        )
    except Exception as exc:
        log.error("Scraper job creation error: %s", exc, exc_info=True)
        raise HTTPException(
            status_code=500,
            detail={"error": "JOB_CREATION_ERROR", "message": str(exc)},
        )


@router.get("/run/{job_id}")
async def get_scraper_status(
    job_id: UUID,
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    await require_byo_access(org, db)
    service = ScraperService(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("/run/{job_id}/results")
async def get_scraper_results(
    job_id: UUID,
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
    cursor: int = 0,
    limit: int = 250,
) -> Dict[str, Any]:
    await require_byo_access(org, db)
    service = ScraperService(db, org.account_id)
    try:
        data = await service.get_results_chunk(job_id, cursor, limit)
    except Exception as exc:
        log.error("Scraper results fetch error: %s", exc, exc_info=True)
        raise HTTPException(
            status_code=500,
            detail={"error": "RESULTS_ERROR", "message": str(exc)},
        )
    return {"ok": True, "data": data}


@router.post("/run/{job_id}/cancel")
async def cancel_scraper_job(
    job_id: UUID,
    org: OrgContext = Depends(require_org_context),
    db: AsyncSession = Depends(get_session),
) -> Dict[str, Any]:
    await require_byo_access(org, db)
    service = ScraperService(db, org.account_id)
    ok = await service.cancel_job(job_id)
    if not ok:
        raise HTTPException(
            status_code=404,
            detail={"error": "NOT_FOUND", "message": f"Job {job_id} not found"},
        )
    return {"ok": True}
