"""Background worker to prefetch SFDC Accounts into DuckDB cache."""

from __future__ import annotations

import asyncio
import json
import logging
from typing import Any, Dict, Optional, Tuple
from uuid import uuid4

from ..services.sfdc_account_cache import SfdcAccountCache
from ..services.salesforce_gateway import (
    SalesforceAuthError,
    SalesforceConnectionError,
    SalesforceGateway,
    SalesforceRateLimitError,
)

logger = logging.getLogger(__name__)

STATE_KEY = "sfdc_prefetch:job:{job_id}:state"
PROGRESS_KEY = "sfdc_prefetch:job:{job_id}:progress"
RESULT_KEY = "sfdc_prefetch:job:{job_id}:result"
IDEMPOTENCY_KEY = "sfdc_prefetch:{account_id}:{key}"

RESULT_TTL = 86400  # 24h
LOCK_WAIT_S = 300


class SfdcAccountPrefetchWorker:
    """Async worker that warms the DuckDB account cache via Bulk API."""

    def __init__(self, redis=None) -> None:
        self.redis = redis

    async def start_prefetch_job(
        self,
        account_id: str,
        integration_id: str,
        gateway: SalesforceGateway,
        idempotency_key: Optional[str] = None,
    ) -> Tuple[str, bool]:
        if self.redis and idempotency_key:
            cache_key = IDEMPOTENCY_KEY.format(
                account_id=account_id, key=idempotency_key
            )
            existing = await self.redis.get(cache_key)
            if existing:
                job_id_existing = (
                    existing.decode("utf-8")
                    if hasattr(existing, "decode")
                    else str(existing)
                )
                return job_id_existing, False

        job_id = str(uuid4())
        if self.redis:
            await self.redis.set(STATE_KEY.format(job_id=job_id), "queued", ex=RESULT_TTL)
            await self._set_progress(job_id, 0, "queued")
            if idempotency_key:
                await self.redis.set(
                    IDEMPOTENCY_KEY.format(account_id=account_id, key=idempotency_key),
                    job_id,
                    ex=RESULT_TTL,
                )

        asyncio.create_task(
            self._run_job(
                job_id=job_id,
                account_id=account_id,
                integration_id=integration_id,
                gateway=gateway,
            )
        )
        return job_id, True

    async def _run_job(
        self,
        job_id: str,
        account_id: str,
        integration_id: str,
        gateway: SalesforceGateway,
    ) -> None:
        await self._set_state(job_id, "processing")
        cache = SfdcAccountCache(
            account_id=account_id,
            integration_id=integration_id,
            redis=self.redis,
        )

        async def _progress(pct: int, phase: str) -> None:
            await self._set_progress(job_id, pct, phase)

        try:
            await self._set_progress(job_id, 5, "prefetching")
            await cache.get_or_prefetch(
                gateway, force_refresh=True, progress_callback=_progress
            )
            # get_or_prefetch returns bool; read meta for count
            meta = await cache.get_metadata()
            account_count = meta.get("account_count") if meta else None
            await self._set_result(
                job_id,
                {
                    "accounts_fetched": account_count,
                    "cache_path": str(cache.db_path),
                },
            )
            await self._set_state(job_id, "completed")
        except SalesforceRateLimitError as exc:
            await self._fail(job_id, f"RATE_LIMITED: {exc}")
        except SalesforceAuthError as exc:
            await self._fail(job_id, f"SFDC_AUTH_REQUIRED: {exc}")
        except SalesforceConnectionError as exc:
            await self._fail(job_id, f"CONNECTION_ERROR: {exc}")
        except Exception as exc:  # noqa: BLE001
            logger.exception("Prefetch job %s failed", job_id)
            await self._fail(job_id, str(exc))

    # ─────────────── Redis helpers ───────────────
    async def _set_state(self, job_id: str, state: str) -> None:
        if not self.redis:
            return
        await self.redis.set(STATE_KEY.format(job_id=job_id), state, ex=RESULT_TTL)

    async def _set_progress(self, job_id: str, pct: int, phase: str) -> None:
        if not self.redis:
            return
        payload = json.dumps({"percent": pct, "phase": phase})
        await self.redis.set(PROGRESS_KEY.format(job_id=job_id), payload, ex=RESULT_TTL)

    async def _set_result(self, job_id: str, payload: Dict[str, Any]) -> None:
        if not self.redis:
            return
        await self.redis.set(
            RESULT_KEY.format(job_id=job_id),
            json.dumps(payload),
            ex=RESULT_TTL,
        )

    async def _fail(self, job_id: str, message: str) -> None:
        await self._set_state(job_id, "failed")
        await self._set_result(job_id, {"error": message})
        await self._set_progress(job_id, 100, "failed")
