"""
Service layer for orchestrating Dashboard and Analytics aggregations, caching, and access controls.
"""

from datetime import date, datetime, timedelta
from uuid import UUID
from zoneinfo import ZoneInfo

from sqlalchemy.ext.asyncio import AsyncSession

from app.core.exceptions import ForbiddenException, ValidationException
from app.core.state_store import get_dashboard_cache, store_dashboard_cache
from app.models.user import User
from app.repositories.dashboard_repository import DashboardRepository
from app.schemas.dashboard import (
    DailyTimeSeriesPoint,
    DashboardOverviewResponse,
    FinancialStats,
    OperationalStats,
    TopPerformersResponse,
)


class DashboardService:
    """Orchestrates analytics retrieval, multi-branch scoping, and 5-minute PostgreSQL caching."""

    def __init__(self, dashboard_repo: DashboardRepository | None = None) -> None:
        self.dashboard_repo = dashboard_repo or DashboardRepository()

    def resolve_date_range(
        self,
        start_date: date | None,
        end_date: date | None,
    ) -> tuple[date, date]:
        """
        Resolve and validate date boundaries.
        Defaults to trailing 30 days if unspecified.
        """
        today = datetime.now(ZoneInfo("Asia/Kathmandu")).date()
        resolved_end = end_date or today
        resolved_start = start_date or (resolved_end - timedelta(days=30))

        if resolved_start > resolved_end:
            raise ValidationException("Start date cannot be after end date.")

        if (resolved_end - resolved_start).days > 366:
            raise ValidationException("Select a period of at most 367 days.")

        return resolved_start, resolved_end

    def resolve_tenant_lab_id(
        self,
        user: User,
        requested_lab_id: UUID | None,
    ) -> UUID | None:
        """
        Enforce branch scoping rules.
        Superadmins can query all branches (None) or specific branch.
        Staff members are strictly restricted to their assigned branch.
        """
        if user.is_superadmin:
            return requested_lab_id

        if not user.lab_id:
            raise ForbiddenException("A branch assignment is required to access analytics.")

        if requested_lab_id and requested_lab_id != user.lab_id:
            raise ForbiddenException("Cannot access dashboard metrics for other branches.")

        return user.lab_id

    def _build_cache_key(
        self,
        lab_id: UUID | None,
        start: date,
        end: date,
        section: str,
    ) -> str:
        """Construct deterministic cache key for PostgreSQL dashboard storage."""
        branch_scope = str(lab_id) if lab_id else "all"
        return f"{branch_scope}:{start.isoformat()}:{end.isoformat()}:{section}"

    async def get_overview(
        self,
        db: AsyncSession,
        user: User,
        start_date: date | None,
        end_date: date | None,
        requested_lab_id: UUID | None = None,
        refresh: bool = False,
    ) -> DashboardOverviewResponse:
        """Return executive consolidated overview across operational, financial, and trends."""
        start, end = self.resolve_date_range(start_date, end_date)
        lab_id = self.resolve_tenant_lab_id(user, requested_lab_id)
        cache_key = self._build_cache_key(lab_id, start, end, "overview")

        if not refresh:
            cached = await get_dashboard_cache(cache_key)
            if cached:
                return DashboardOverviewResponse.model_validate(cached)

        operational = await self.dashboard_repo.get_operational_stats(db, lab_id, start, end)
        financial = await self.dashboard_repo.get_financial_stats(db, lab_id, start, end)
        trend = await self.dashboard_repo.get_daily_trends(db, lab_id, start, end)
        top_tests = await self.dashboard_repo.get_top_tests(db, lab_id, start, end, limit=5)
        top_packages = await self.dashboard_repo.get_top_packages(db, lab_id, start, end, limit=5)
        top_referrers = await self.dashboard_repo.get_top_referrers(db, lab_id, start, end, limit=5)

        response = DashboardOverviewResponse(
            period_start=start,
            period_end=end,
            lab_id=lab_id,
            operational=operational,
            financial=financial,
            trend=trend,
            top_tests=top_tests,
            top_packages=top_packages,
            top_referrers=top_referrers,
        )

        await store_dashboard_cache(
            cache_key,
            response.model_dump(mode="json"),
            expires_in_seconds=300,
        )
        return response

    async def get_operational_stats(
        self,
        db: AsyncSession,
        user: User,
        start_date: date | None,
        end_date: date | None,
        requested_lab_id: UUID | None = None,
        refresh: bool = False,
    ) -> OperationalStats:
        """Return operational volumes, sample/verification queues, and order status funnels."""
        start, end = self.resolve_date_range(start_date, end_date)
        lab_id = self.resolve_tenant_lab_id(user, requested_lab_id)
        cache_key = self._build_cache_key(lab_id, start, end, "operational")

        if not refresh:
            cached = await get_dashboard_cache(cache_key)
            if cached:
                return OperationalStats.model_validate(cached)

        stats = await self.dashboard_repo.get_operational_stats(db, lab_id, start, end)

        await store_dashboard_cache(
            cache_key,
            stats.model_dump(mode="json"),
            expires_in_seconds=300,
        )
        return stats

    async def get_financial_stats(
        self,
        db: AsyncSession,
        user: User,
        start_date: date | None,
        end_date: date | None,
        requested_lab_id: UUID | None = None,
        refresh: bool = False,
    ) -> FinancialStats:
        """Return financial metrics including billing aggregates, payment tenders, and commissions."""
        start, end = self.resolve_date_range(start_date, end_date)
        lab_id = self.resolve_tenant_lab_id(user, requested_lab_id)
        cache_key = self._build_cache_key(lab_id, start, end, "financial")

        if not refresh:
            cached = await get_dashboard_cache(cache_key)
            if cached:
                return FinancialStats.model_validate(cached)

        stats = await self.dashboard_repo.get_financial_stats(db, lab_id, start, end)

        await store_dashboard_cache(
            cache_key,
            stats.model_dump(mode="json"),
            expires_in_seconds=300,
        )
        return stats

    async def get_daily_trends(
        self,
        db: AsyncSession,
        user: User,
        start_date: date | None,
        end_date: date | None,
        requested_lab_id: UUID | None = None,
        refresh: bool = False,
    ) -> list[DailyTimeSeriesPoint]:
        """Return contiguous daily time-series points across the requested date window."""
        start, end = self.resolve_date_range(start_date, end_date)
        lab_id = self.resolve_tenant_lab_id(user, requested_lab_id)
        cache_key = self._build_cache_key(lab_id, start, end, "trends")

        if not refresh:
            cached = await get_dashboard_cache(cache_key)
            if cached and "items" in cached:
                return [DailyTimeSeriesPoint.model_validate(item) for item in cached["items"]]

        trends = await self.dashboard_repo.get_daily_trends(db, lab_id, start, end)

        payload = {"items": [item.model_dump(mode="json") for item in trends]}
        await store_dashboard_cache(
            cache_key,
            payload,
            expires_in_seconds=300,
        )
        return trends

    async def get_top_performers(
        self,
        db: AsyncSession,
        user: User,
        start_date: date | None,
        end_date: date | None,
        requested_lab_id: UUID | None = None,
        refresh: bool = False,
    ) -> TopPerformersResponse:
        """Return top tests, top packages, and top referring entities."""
        start, end = self.resolve_date_range(start_date, end_date)
        lab_id = self.resolve_tenant_lab_id(user, requested_lab_id)
        cache_key = self._build_cache_key(lab_id, start, end, "top_performers")

        if not refresh:
            cached = await get_dashboard_cache(cache_key)
            if cached:
                return TopPerformersResponse.model_validate(cached)

        top_tests = await self.dashboard_repo.get_top_tests(db, lab_id, start, end, limit=5)
        top_packages = await self.dashboard_repo.get_top_packages(db, lab_id, start, end, limit=5)
        top_referrers = await self.dashboard_repo.get_top_referrers(db, lab_id, start, end, limit=5)

        performers = TopPerformersResponse(
            top_tests=top_tests,
            top_packages=top_packages,
            top_referrers=top_referrers,
        )

        await store_dashboard_cache(
            cache_key,
            performers.model_dump(mode="json"),
            expires_in_seconds=300,
        )
        return performers
