"""Plan repository for data access."""

from datetime import date, timedelta
from typing import Any
from uuid import UUID

from sqlalchemy import delete, select, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from identity_plan_kit.plans.domain.entities import (
    Feature,
    PeriodType,
    Plan,
    PlanLimit,
    UserPlan,
)
from identity_plan_kit.plans.models.feature import FeatureModel
from identity_plan_kit.plans.models.plan import PlanModel
from identity_plan_kit.plans.models.plan_limit import PlanLimitModel
from identity_plan_kit.plans.models.plan_permission import PlanPermissionModel
from identity_plan_kit.plans.models.user_plan import UserPlanModel
from identity_plan_kit.shared.logging import get_logger
from identity_plan_kit.shared.uuid7 import uuid7

logger = get_logger(__name__)


class PlanRepository:
    """Repository for plan data access."""

    def __init__(self, session: AsyncSession) -> None:
        self._session = session

    async def get_plan_by_id(self, plan_id: UUID) -> Plan | None:
        """
        Get plan by ID with permissions and limits.

        Args:
            plan_id: Plan UUID

        Returns:
            Plan entity or None
        """
        stmt = (
            select(PlanModel)
            .options(
                selectinload(PlanModel.permissions).selectinload(PlanPermissionModel.permission),
                selectinload(PlanModel.limits).selectinload(PlanLimitModel.feature),
            )
            .where(PlanModel.id == plan_id)
        )
        result = await self._session.execute(stmt)
        model = result.scalar_one_or_none()

        if model is None:
            return None

        return self._plan_to_entity(model)

    async def get_plan_by_code(self, code: str) -> Plan | None:
        """
        Get plan by code.

        Args:
            code: Plan code (e.g., "pro")

        Returns:
            Plan entity or None
        """
        stmt = (
            select(PlanModel)
            .options(
                selectinload(PlanModel.permissions).selectinload(PlanPermissionModel.permission),
                selectinload(PlanModel.limits).selectinload(PlanLimitModel.feature),
            )
            .where(PlanModel.code == code)
        )
        result = await self._session.execute(stmt)
        model = result.scalar_one_or_none()

        if model is None:
            return None

        return self._plan_to_entity(model)

    async def get_user_active_plan(self, user_id: UUID) -> UserPlan | None:
        """
        Get user's active plan.

        Args:
            user_id: User UUID

        Returns:
            UserPlan entity or None
        """
        today = date.today()
        stmt = (
            select(UserPlanModel)
            .options(selectinload(UserPlanModel.plan))
            .where(
                UserPlanModel.user_id == user_id,
                UserPlanModel.started_at <= today,
                UserPlanModel.ends_at >= today,
            )
            .order_by(UserPlanModel.ends_at.desc())
            .limit(1)
        )
        result = await self._session.execute(stmt)
        model = result.scalar_one_or_none()

        if model is None:
            return None

        return self._user_plan_to_entity(model)

    async def get_plan_limit(
        self,
        plan_id: UUID,
        feature_code: str,
    ) -> PlanLimit | None:
        """
        Get limit for a feature in a plan.

        Args:
            plan_id: Plan UUID
            feature_code: Feature code

        Returns:
            PlanLimit entity or None
        """
        stmt = (
            select(PlanLimitModel)
            .join(FeatureModel)
            .options(selectinload(PlanLimitModel.feature))
            .where(
                PlanLimitModel.plan_id == plan_id,
                FeatureModel.code == feature_code,
            )
        )
        result = await self._session.execute(stmt)
        model = result.scalar_one_or_none()

        if model is None:
            return None

        return self._limit_to_entity(model)

    async def get_feature_by_code(self, code: str) -> Feature | None:
        """
        Get feature by code.

        Args:
            code: Feature code

        Returns:
            Feature entity or None
        """
        stmt = select(FeatureModel).where(FeatureModel.code == code)
        result = await self._session.execute(stmt)
        model = result.scalar_one_or_none()

        if model is None:
            return None

        return Feature(id=model.id, code=model.code, name=model.name)

    def _plan_to_entity(self, model: PlanModel) -> Plan:
        """Convert plan model to entity."""
        permissions = {pp.permission.code for pp in model.permissions if pp.permission is not None}
        limits = {
            pl.feature.code: self._limit_to_entity(pl)
            for pl in model.limits
            if pl.feature is not None
        }
        return Plan(
            id=model.id,
            code=model.code,
            name=model.name,
            permissions=permissions,
            limits=limits,
        )

    def _user_plan_to_entity(self, model: UserPlanModel) -> UserPlan:
        """Convert user plan model to entity."""
        return UserPlan(
            id=model.id,
            user_id=model.user_id,
            plan_id=model.plan_id,
            plan_code=model.plan.code if model.plan else "",
            started_at=model.started_at,
            ends_at=model.ends_at,
            custom_limits=model.custom_limits or {},
        )

    def _limit_to_entity(self, model: PlanLimitModel) -> PlanLimit:
        """Convert limit model to entity."""
        period = PeriodType(model.period) if model.period else None
        return PlanLimit(
            id=model.id,
            plan_id=model.plan_id,
            feature_id=model.feature_id,
            feature_code=model.feature.code if model.feature else "",
            limit=model.feature_limit,
            period=period,
        )

    async def create_user_plan(
        self,
        user_id: UUID,
        plan_id: UUID,
        started_at: date | None = None,
        ends_at: date | None = None,
        custom_limits: dict[str, Any] | None = None,
    ) -> UserPlan:
        """
        Create a user plan assignment.

        Args:
            user_id: User UUID
            plan_id: Plan UUID to assign
            started_at: Plan start date (defaults to today)
            ends_at: Plan end date (defaults to 100 years from now for "lifetime")
            custom_limits: Optional custom limits override

        Returns:
            Created UserPlan entity
        """
        today = date.today()
        model = UserPlanModel(
            id=uuid7(),
            user_id=user_id,
            plan_id=plan_id,
            started_at=started_at or today,
            ends_at=ends_at or date(today.year + 100, today.month, today.day),
            custom_limits=custom_limits or {},
        )
        self._session.add(model)
        await self._session.flush()

        # Reload with relationships
        await self._session.refresh(model, ["plan"])

        logger.info(
            "user_plan_created",
            user_id=str(user_id),
            plan_id=str(plan_id),
        )

        return self._user_plan_to_entity(model)

    async def get_user_plan_by_id(self, user_plan_id: UUID) -> UserPlan | None:
        """
        Get a user plan by its ID.

        Args:
            user_plan_id: UserPlan UUID

        Returns:
            UserPlan entity or None
        """
        stmt = (
            select(UserPlanModel)
            .options(selectinload(UserPlanModel.plan))
            .where(UserPlanModel.id == user_plan_id)
        )
        result = await self._session.execute(stmt)
        model = result.scalar_one_or_none()

        if model is None:
            return None

        return self._user_plan_to_entity(model)

    async def update_user_plan(
        self,
        user_plan_id: UUID,
        ends_at: date | None = None,
        custom_limits: dict[str, Any] | None = None,
    ) -> UserPlan | None:
        """
        Update a user plan.

        Args:
            user_plan_id: UserPlan UUID
            ends_at: New end date (None to keep current)
            custom_limits: New custom limits (None to keep current)

        Returns:
            Updated UserPlan entity or None if not found
        """
        # Build update values
        values: dict[str, Any] = {}
        if ends_at is not None:
            values["ends_at"] = ends_at
        if custom_limits is not None:
            values["custom_limits"] = custom_limits

        if not values:
            # Nothing to update, just return current
            return await self.get_user_plan_by_id(user_plan_id)

        stmt = (
            update(UserPlanModel)
            .where(UserPlanModel.id == user_plan_id)
            .values(**values)
            .returning(UserPlanModel.id)
        )
        result = await self._session.execute(stmt)
        updated_id = result.scalar_one_or_none()

        if updated_id is None:
            return None

        await self._session.flush()

        logger.info(
            "user_plan_updated",
            user_plan_id=str(user_plan_id),
            updates=list(values.keys()),
        )

        return await self.get_user_plan_by_id(user_plan_id)

    async def cancel_user_plan(
        self,
        user_id: UUID,
        immediate: bool = False,
    ) -> bool:
        """
        Cancel user's active plan.

        Args:
            user_id: User UUID
            immediate: If True, cancel immediately (ends_at = yesterday).
                      If False, plan remains active until current ends_at.

        Returns:
            True if a plan was cancelled, False if no active plan found
        """
        user_plan = await self.get_user_active_plan(user_id)
        if user_plan is None:
            return False

        if immediate:
            # Set ends_at to yesterday to immediately deactivate
            new_ends_at = date.today() - timedelta(days=1)
        else:
            # Keep current ends_at (plan expires naturally)
            # Just mark it as cancelled by not extending
            return True

        stmt = (
            update(UserPlanModel)
            .where(UserPlanModel.id == user_plan.id)
            .values(ends_at=new_ends_at)
        )
        await self._session.execute(stmt)
        await self._session.flush()

        logger.info(
            "user_plan_cancelled",
            user_id=str(user_id),
            user_plan_id=str(user_plan.id),
            immediate=immediate,
        )

        return True

    async def expire_user_plan(
        self,
        user_plan_id: UUID,
    ) -> bool:
        """
        Immediately expire a specific user plan.

        Args:
            user_plan_id: UserPlan UUID to expire

        Returns:
            True if plan was expired, False if not found
        """
        yesterday = date.today() - timedelta(days=1)
        stmt = (
            update(UserPlanModel)
            .where(UserPlanModel.id == user_plan_id)
            .values(ends_at=yesterday)
            .returning(UserPlanModel.id)
        )
        result = await self._session.execute(stmt)
        expired_id = result.scalar_one_or_none()

        if expired_id is None:
            return False

        await self._session.flush()

        logger.info(
            "user_plan_expired",
            user_plan_id=str(user_plan_id),
        )

        return True

    async def delete_user_plan(
        self,
        user_plan_id: UUID,
    ) -> bool:
        """
        Hard delete a user plan record.

        Note: This also deletes associated usage records (CASCADE).
        Use with caution - prefer expire_user_plan for soft delete.

        Args:
            user_plan_id: UserPlan UUID to delete

        Returns:
            True if deleted, False if not found
        """
        stmt = (
            delete(UserPlanModel)
            .where(UserPlanModel.id == user_plan_id)
            .returning(UserPlanModel.id)
        )
        result = await self._session.execute(stmt)
        deleted_id = result.scalar_one_or_none()

        if deleted_id is None:
            return False

        await self._session.flush()

        logger.warning(
            "user_plan_deleted",
            user_plan_id=str(user_plan_id),
        )

        return True
