"""Background pull worker for Forecast opportunity ingestion."""

from __future__ import annotations

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

from ..db import AsyncSessionLocal
from ..services.forecast_canonicalizer import ForecastCanonicalizer
from ..services.forecast_soql_builder import ForecastSoqlBuilder
from ..services.salesforce_gateway import SalesforceGateway
from ..api.progress import get_redis

logger = logging.getLogger(__name__)

PROGRESS_KEY = "job:{job_id}:progress"
RESULT_KEY = "job:{job_id}:result"
RESULT_TTL = 3600


class ForecastPullWorker:
    """Pull opportunities with queryMore pagination and canonicalize rows."""

    def __init__(
        self,
        account_id: str,
        integration_id: Optional[str],
        config: Dict[str, Any],
        mappings: List[Dict[str, Any]],
        job_id: Optional[str] = None,
    ) -> None:
        self.account_id = account_id
        self.integration_id = integration_id
        self.config = config or {}
        self.mappings = mappings or []
        self.job_id = job_id or ""
        self._builder = ForecastSoqlBuilder()

    async def run(self) -> Dict[str, Any]:
        redis = await self._get_redis_optional()
        await self._set_progress(redis, 5, phase="initializing")

        async with AsyncSessionLocal() as session:
            gateway = await SalesforceGateway.for_tenant(self.account_id, session)

            soql = self._builder.build_opportunity_query(self.config)
            await self._set_progress(redis, 15, phase="querying")

            records = await self._pull_all_records(gateway, soql, redis)

        canonicalizer = ForecastCanonicalizer(self.config, self.mappings)
        canonical_rows = [canonicalizer.canonicalize(rec) for rec in records]
        result = {
            "rows_pulled": len(records),
            "canonical_rows": canonical_rows,
            "status": "completed",
        }

        await self._store_result(redis, result)
        await self._set_progress(redis, 100, phase="final")
        return result

    async def _pull_all_records(
        self,
        gateway: SalesforceGateway,
        soql: str,
        redis,
    ) -> List[Dict[str, Any]]:
        all_records: List[Dict[str, Any]] = []
        result = await gateway.soql(soql)
        all_records.extend(result.get("records", []) if isinstance(result, dict) else [])

        while isinstance(result, dict) and result.get("nextRecordsUrl"):
            await self._set_progress(redis, min(90, 20 + len(all_records) // 250), phase="pagination")
            result = await gateway.fetch_next(result["nextRecordsUrl"])
            all_records.extend(result.get("records", []) if isinstance(result, dict) else [])
            if len(all_records) > 50000:
                logger.warning("Hit safety row limit (50k) for forecast pull")
                break

        return all_records

    async def _set_progress(self, redis, percent: int, phase: str) -> None:
        if not redis or not self.job_id:
            return
        payload = json.dumps({"percent": percent, "phase": phase})
        try:
            await redis.set(PROGRESS_KEY.format(job_id=self.job_id), payload, ex=RESULT_TTL)
        except Exception as exc:  # pragma: no cover - best effort
            logger.debug("Progress update failed: %s", exc)

    async def _store_result(self, redis, payload: Dict[str, Any]) -> None:
        if not redis or not self.job_id:
            return
        try:
            await redis.set(RESULT_KEY.format(job_id=self.job_id), json.dumps(payload), ex=RESULT_TTL)
        except Exception as exc:  # pragma: no cover - best effort
            logger.debug("Result storage failed: %s", exc)

    async def _get_redis_optional(self):
        try:
            return await get_redis()
        except Exception:  # pragma: no cover - optional
            return None


async def run_forecast_pull_job(
    job_id: str,
    account_id: str,
    integration_id: Optional[str],
    config: Dict[str, Any],
    mappings: List[Dict[str, Any]],
) -> Dict[str, Any]:
    """Entry point for background execution."""
    worker = ForecastPullWorker(
        account_id=account_id,
        integration_id=integration_id,
        config=config,
        mappings=mappings,
        job_id=job_id,
    )
    return await worker.run()
