"""Company List Builder API for Google Sheets sidebar."""

from __future__ import annotations

import asyncio
import json
import logging
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, Depends, Header, HTTPException, Security, status
from fastapi.security import HTTPAuthorizationCredentials
from typing import TYPE_CHECKING

# Lazy import google.cloud.bigquery to avoid GCP credential discovery hang at startup
if TYPE_CHECKING:
    pass

def _get_bq_config():
    """Lazily import BigQuery config to avoid startup hang."""
    from google.cloud import bigquery
    return bigquery.QueryJobConfig
from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession

from ...auth_security import require_account, DEV_ACCOUNT_ID, http_bearer, api_key_hdr
from ... import settings as saas_settings
from ...db import get_session
from ..progress import get_redis
from ...services.list_builder_quota import ListBuilderQuota

# Lazy imports for GCP-heavy modules to avoid startup hang
_company_list_service_module = None
_list_builder_worker_module = None
_query_builder = None

# Redis key constants - duplicated to avoid module import
PRIMARY_KEYS = {
    "state": "listbuilder:export:{job_id}:state",
    "progress": "listbuilder:export:{job_id}:progress",
    "result": "listbuilder:export:{job_id}:result",
}
# Legacy fallback (older prefix)
LEGACY_KEYS = {
    "state": "list_builder:job:{job_id}:state",
    "progress": "list_builder:job:{job_id}:progress",
    "result": "list_builder:job:{job_id}:result",
}

def _get_query_builder():
    """Lazily create CompanyQueryBuilder to avoid GCP init at startup."""
    global _query_builder, _company_list_service_module
    if _query_builder is None:
        if _company_list_service_module is None:
            from ...services import company_list_service
            _company_list_service_module = company_list_service
        _query_builder = _company_list_service_module.CompanyQueryBuilder()
    return _query_builder

def _get_ListBuilderWorker():
    """Lazily import ListBuilderWorker to avoid GCP init at startup."""
    global _list_builder_worker_module
    if _list_builder_worker_module is None:
        from ...workers import list_builder_worker
        _list_builder_worker_module = list_builder_worker
    return _list_builder_worker_module.ListBuilderWorker

logger = logging.getLogger(__name__)

router = APIRouter(
    prefix="/api/v2/foundrygraph/companies", tags=["company-list-builder"]
)


class CompanyFilters(BaseModel):
    industry: Optional[List[str]] = None
    country: Optional[List[str]] = None
    state: Optional[List[str]] = None
    employee_band: Optional[List[str]] = None
    revenue_band: Optional[List[str]] = None
    employee_min: Optional[int] = None
    employee_max: Optional[int] = None
    revenue_min: Optional[int] = None
    revenue_max: Optional[int] = None
    has_domain: Optional[bool] = None
    is_parent: Optional[bool] = None
    is_subsidiary: Optional[bool] = None


class SearchRequest(BaseModel):
    filters: CompanyFilters = Field(default_factory=CompanyFilters)
    limit: int = Field(25, ge=1, le=1000)
    offset: int = Field(0, ge=0)
    columns: Optional[List[str]] = None
    sort_by: Optional[str] = None
    sort_dir: Optional[str] = Field("desc", description="asc|desc")


class SearchResponse(BaseModel):
    rows: List[Dict[str, Any]]
    has_more: bool
    next_offset: Optional[int] = None


class ExportRequest(BaseModel):
    filters: CompanyFilters = Field(default_factory=CompanyFilters)
    limit: int = Field(1000, ge=1, le=20000)
    columns: Optional[List[str]] = None
    sort_by: Optional[str] = None
    sort_dir: Optional[str] = Field("desc", description="asc|desc")


class ExportStartResponse(BaseModel):
    job_id: str
    status: str
    allowed_rows: int


class ExportStatusResponse(BaseModel):
    job_id: str
    state: str
    progress: int
    result: Optional[Dict[str, Any]] = None


class FieldOptionsResponse(BaseModel):
    filters: Dict[str, List[str]]
    column_presets: List[Dict[str, Any]]
    available_columns: List[str]


class QuotaResponse(BaseModel):
    tier: str
    daily_limit: int
    daily_used: int
    daily_remaining: int
    monthly_limit: int
    monthly_used: int
    monthly_remaining: int
    preview_limit: int
    max_columns: int


async def _run_bigquery(query: str, params: List[Any], max_results: int) -> List[Any]:
    """Run a BigQuery query in a thread to avoid blocking the event loop."""
    def _run():
        QueryJobConfig = _get_bq_config()
        qb = _get_query_builder()
        job = qb.client.query(
            query, job_config=QueryJobConfig(query_parameters=params)
        )
        return list(job.result(max_results=max_results))

    return await asyncio.to_thread(_run)


async def _require_account_dev_fallback(
    bearer: Optional[HTTPAuthorizationCredentials] = Security(http_bearer),
    api_key: Optional[str] = Security(api_key_hdr),
    db: AsyncSession = Depends(get_session),
) -> str:
    """
    Use standard account auth; in development, fall back to DEV_ACCOUNT_ID if auth fails.
    """
    try:
        return await require_account(bearer=bearer, api_key=api_key, db=db)
    except HTTPException:
        if saas_settings.ENV.lower() in ("dev", "development"):
            logger.warning("Dev fallback to DEV_ACCOUNT_ID for company list builder endpoint")
            return DEV_ACCOUNT_ID
        raise


async def _get_redis_safe(required: bool = False):
    try:
        return await get_redis()
    except Exception as exc:  # noqa: BLE001
        logger.warning("Redis unavailable for list builder: %s", exc)
        if required:
            raise HTTPException(status_code=503, detail="Cache unavailable")
        return None


@router.get("/fields", response_model=FieldOptionsResponse)
async def get_field_options(
    account_id: str = Depends(_require_account_dev_fallback),
) -> FieldOptionsResponse:
    redis = await _get_redis_safe()
    cache_key = "listbuilder:fields:v2"
    if redis:
        cached = await redis.get(cache_key)
        if cached:
            try:
                payload = json.loads(cached)
                return FieldOptionsResponse(**payload)
            except Exception:
                pass

    qb = _get_query_builder()
    options = await qb.fetch_filter_options()
    payload = {
        "filters": options,
        "column_presets": qb.column_presets(),
        "available_columns": qb.available_columns(),
    }

    # Only cache if we have at least one non-empty option set to avoid caching empty defaults
    has_values = any(options.get(key) for key in ("industries", "countries", "states", "revenue_bands", "employee_bands"))
    if redis and has_values:
        try:
            await redis.set(cache_key, json.dumps(payload), ex=86400)
        except Exception:
            logger.warning("Failed to cache list builder fields")

    return FieldOptionsResponse(**payload)


@router.get("/quota", response_model=QuotaResponse)
async def get_quota(
    account_id: str = Depends(_require_account_dev_fallback),
    db: AsyncSession = Depends(get_session),
) -> QuotaResponse:
    redis = await _get_redis_safe()
    quota_service = ListBuilderQuota(db=db, redis=redis)
    tier_name, tier_config, quota = await quota_service.get_limits(account_id)

    daily_used = int(quota.list_builder_daily_exports or 0)
    monthly_used = int(quota.list_builder_monthly_exports or 0)

    return QuotaResponse(
        tier=tier_name,
        daily_limit=tier_config.list_builder_daily_rows,
        daily_used=daily_used,
        daily_remaining=max(0, tier_config.list_builder_daily_rows - daily_used),
        monthly_limit=tier_config.list_builder_monthly_rows,
        monthly_used=monthly_used,
        monthly_remaining=max(
            0, tier_config.list_builder_monthly_rows - monthly_used
        ),
        preview_limit=tier_config.list_builder_preview_rows,
        max_columns=tier_config.list_builder_max_columns,
    )


@router.post("/search", response_model=SearchResponse)
async def search_companies(
    request: SearchRequest,
    account_id: str = Depends(_require_account_dev_fallback),
    db: AsyncSession = Depends(get_session),
) -> SearchResponse:
    redis = await _get_redis_safe()
    quota_service = ListBuilderQuota(db=db, redis=redis)
    _, tier_config, _ = await quota_service.get_limits(account_id)

    limit = min(request.limit, tier_config.list_builder_preview_rows)
    filters = request.filters.dict(exclude_none=True)

    qb = _get_query_builder()
    query, params, selected_columns = qb.build_query(
        filters=filters,
        columns=request.columns or qb.DEFAULT_COLUMNS,
        limit=limit,
        offset=request.offset,
        sort_by=request.sort_by,
        sort_dir=request.sort_dir,
        include_extra_row=True,
        max_columns=tier_config.list_builder_max_columns,
    )

    rows = await _run_bigquery(query, params, max_results=limit + 1)
    has_more = len(rows) > limit
    trimmed = rows[:limit]
    formatted = qb.format_rows(trimmed, selected_columns)

    return SearchResponse(
        rows=formatted,
        has_more=has_more,
        next_offset=request.offset + limit if has_more else None,
    )


@router.post("/export", response_model=ExportStartResponse)
async def start_export(
    request: ExportRequest,
    account_id: str = Depends(_require_account_dev_fallback),
    db: AsyncSession = Depends(get_session),
    idempotency_key: Optional[str] = Header(default=None, alias="Idempotency-Key"),
) -> ExportStartResponse:
    redis = await _get_redis_safe()
    quota_service = ListBuilderQuota(db=db, redis=redis)
    limits = await quota_service.check_can_export(account_id, request.limit)

    if not limits["allowed"]:
        tier = limits.get("tier", "free")
        raise HTTPException(
            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
            detail={
                "error": "quota_exceeded",
                "message": f"Export quota exceeded. {limits['daily_remaining']} rows remaining today, {limits['monthly_remaining']} this month.",
                "daily_remaining": limits["daily_remaining"],
                "monthly_remaining": limits["monthly_remaining"],
                "tier": tier,
                "upgrade_hint": "Upgrade to Scale for 10,000 rows/month" if tier != "scale" else None,
            },
        )

    active_jobs = await quota_service.get_active_job_count(account_id)
    if active_jobs >= 3:
        raise HTTPException(
            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
            detail={"error": "too_many_jobs", "message": "Too many exports in flight"},
        )

    _, tier_config, _ = await quota_service.get_limits(account_id)
    filters = request.filters.dict(exclude_none=True)

    # Normalize columns via builder (without running the query)
    qb = _get_query_builder()
    _query, _params, selected_columns = qb.build_query(
        filters=filters,
        columns=request.columns or qb.DEFAULT_COLUMNS,
        limit=limits["allowed_rows"],
        offset=0,
        sort_by=request.sort_by,
        sort_dir=request.sort_dir,
        include_extra_row=False,
        max_columns=tier_config.list_builder_max_columns,
    )

    ListBuilderWorker = _get_ListBuilderWorker()
    worker = ListBuilderWorker(redis=redis)
    job_id, started = await worker.start_export_job(
        account_id=account_id,
        filters=filters,
        columns=selected_columns,
        limit=limits["allowed_rows"],
        sort_by=request.sort_by,
        sort_dir=request.sort_dir,
        idempotency_key=idempotency_key or "",
    )

    status_label = "queued" if started else "duplicate"
    return ExportStartResponse(
        job_id=job_id, status=status_label, allowed_rows=limits["allowed_rows"]
    )


@router.get("/export/{job_id}", response_model=ExportStatusResponse)
async def poll_export(
    job_id: str,
    account_id: str = Depends(require_account),
) -> ExportStatusResponse:
    redis = await _get_redis_safe(required=True)

    keyset = None
    state_raw = await redis.get(PRIMARY_KEYS["state"].format(job_id=job_id))
    if state_raw:
        keyset = PRIMARY_KEYS
    else:
        legacy_state = await redis.get(LEGACY_KEYS["state"].format(job_id=job_id))
        if legacy_state:
            keyset = LEGACY_KEYS
            state_raw = legacy_state
    if not state_raw or not keyset:
        raise HTTPException(status_code=404, detail="Job not found")

    state = (
        state_raw.decode("utf-8") if isinstance(state_raw, (bytes, bytearray)) else str(state_raw)
    )

    progress_raw = await redis.get(keyset["progress"].format(job_id=job_id))
    progress = 0
    if progress_raw:
        try:
            progress = int(progress_raw.decode("utf-8") if isinstance(progress_raw, (bytes, bytearray)) else progress_raw)
        except Exception:
            progress = 0

    result_raw = await redis.get(keyset["result"].format(job_id=job_id))
    result: Optional[Dict[str, Any]] = None
    if result_raw:
        try:
            parsed = result_raw.decode("utf-8") if isinstance(result_raw, (bytes, bytearray)) else result_raw
            result = json.loads(parsed)
        except Exception:
            result = {"raw": str(result_raw)}

    return ExportStatusResponse(
        job_id=job_id, state=state, progress=progress, result=result
    )
