"""
Repository for coupon data access and validation
"""
from datetime import datetime, timezone
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import and_, select
from sqlalchemy.orm import selectinload

from app.models.coupon import CouponModel, CouponRedemptionModel
from shared.subscription.coupons import (
    Coupon,
    CouponRedemption,
    CouponValidationResult,
    DiscountType,
    calculate_trial_end_date,
    format_discount_description
)


class CouponRepository:
    """Data access layer for coupon operations"""

    def __init__(self, db: AsyncSession):
        self.db = db

    async def get_coupon_by_code(self, code: str) -> Optional[Coupon]:
        """
        Retrieve coupon by code.
        Returns None if not found.
        """
        result = await self.db.execute(
            select(CouponModel).where(CouponModel.code == code.upper())
        )
        coupon_model = result.scalar_one_or_none()

        if not coupon_model:
            return None

        return self._model_to_dataclass(coupon_model)

    async def validate_coupon(
        self,
        code: str,
        tenant_id: str
    ) -> CouponValidationResult:
        """
        Validate a coupon code for a specific tenant.
        Checks: existence, validity, redemption limits, and if tenant already used it.
        """
        # Get coupon
        coupon = await self.get_coupon_by_code(code)
        if not coupon:
            return CouponValidationResult(
                valid=False,
                coupon=None,
                error_message="Código de cupón inválido",
                discount_preview=None
            )

        # Check if coupon can be redeemed
        can_redeem, reason = coupon.can_be_redeemed()
        if not can_redeem:
            error_messages = {
                "Coupon is inactive": "Este cupón no está activo",
                "Coupon is not yet valid": "Este cupón aún no es válido",
                "Coupon has expired": "Este cupón ha expirado",
                "Coupon has reached maximum redemptions": "Este cupón ha alcanzado su límite de usos"
            }
            return CouponValidationResult(
                valid=False,
                coupon=coupon,
                error_message=error_messages.get(reason, reason),
                discount_preview=None
            )

        # Check if tenant already redeemed this coupon
        result = await self.db.execute(
            select(CouponRedemptionModel).where(
                and_(
                    CouponRedemptionModel.tenant_id == tenant_id,
                    CouponRedemptionModel.coupon_code == code.upper()
                )
            )
        )
        existing_redemption = result.scalar_one_or_none()

        if existing_redemption:
            return CouponValidationResult(
                valid=False,
                coupon=coupon,
                error_message="Ya has utilizado este cupón",
                discount_preview=None
            )

        # Generate discount preview
        discount_preview = self._generate_discount_preview(coupon)

        return CouponValidationResult(
            valid=True,
            coupon=coupon,
            error_message=None,
            discount_preview=discount_preview
        )

    async def redeem_coupon(
        self,
        code: str,
        tenant_id: Optional[str],
        base_trial_days: int = 0
    ) -> tuple[bool, Optional[CouponRedemption], Optional[str]]:
        """
        Redeem a coupon for a tenant.
        For tenant-independent registrations, tenant_id can be None initially.
        Returns (success, redemption, error_message)
        """
        # For tenant-independent registrations, skip tenant validation
        if tenant_id:
            # Validate first
            validation = await self.validate_coupon(code, tenant_id)
            if not validation.valid:
                return False, None, validation.error_message
            coupon = validation.coupon
        else:
            # Just get the coupon and validate its general availability
            coupon = await self.get_coupon_by_code(code)
            if not coupon:
                return False, None, "Código de cupón inválido"

            # Check if coupon can be redeemed
            can_redeem, reason = coupon.can_be_redeemed()
            if not can_redeem:
                error_messages = {
                    "Coupon is inactive": "Este cupón no está activo",
                    "Coupon is not yet valid": "Este cupón aún no es válido",
                    "Coupon has expired": "Este cupón ha expirado",
                    "Coupon has reached maximum redemptions": "Este cupón ha alcanzado su límite de usos"
                }
                return False, None, error_messages.get(reason, reason)

        # Calculate discount applied
        discount_applied = self._calculate_discount_applied(
            coupon,
            base_trial_days
        )

        # Only create redemption record if tenant_id is provided
        # For tenant-independent subscriptions, skip redemption record creation
        if tenant_id:
            # Create redemption record
            redemption_model = CouponRedemptionModel(
                tenant_id=tenant_id,
                coupon_code=code.upper(),
                redeemed_at=datetime.now(timezone.utc),
                discount_applied=discount_applied,
                extra_data={
                    "coupon_type": coupon.discount_type.value,
                    "coupon_value": coupon.discount_value
                }
            )

            self.db.add(redemption_model)

            # Increment coupon redemption count
            result = await self.db.execute(
                select(CouponModel).where(CouponModel.code == code.upper())
            )
            coupon_model = result.scalar_one_or_none()
            if coupon_model:
                coupon_model.current_redemptions += 1

            try:
                await self.db.commit()
                await self.db.refresh(redemption_model)

                redemption = CouponRedemption(
                    id=str(redemption_model.id),
                    tenant_id=redemption_model.tenant_id,
                    coupon_code=redemption_model.coupon_code,
                    redeemed_at=redemption_model.redeemed_at,
                    discount_applied=redemption_model.discount_applied,
                    extra_data=redemption_model.extra_data
                )

                return True, redemption, None

            except Exception as e:
                await self.db.rollback()
                return False, None, f"Error al aplicar el cupón: {str(e)}"
        else:
            # For tenant-independent subscriptions, return discount without creating redemption
            # The redemption will be created when the tenant is linked
            redemption = CouponRedemption(
                id="pending",  # Temporary ID
                tenant_id="pending",  # Will be set during tenant linking
                coupon_code=code.upper(),
                redeemed_at=datetime.now(timezone.utc),
                discount_applied=discount_applied,
                extra_data={
                    "coupon_type": coupon.discount_type.value,
                    "coupon_value": coupon.discount_value
                }
            )
            return True, redemption, None

    async def get_redemption_by_tenant_and_code(
        self,
        tenant_id: str,
        code: str
    ) -> Optional[CouponRedemption]:
        """Get existing redemption for tenant and coupon code"""
        result = await self.db.execute(
            select(CouponRedemptionModel).where(
                and_(
                    CouponRedemptionModel.tenant_id == tenant_id,
                    CouponRedemptionModel.coupon_code == code.upper()
                )
            )
        )
        redemption_model = result.scalar_one_or_none()

        if not redemption_model:
            return None

        return CouponRedemption(
            id=str(redemption_model.id),
            tenant_id=redemption_model.tenant_id,
            coupon_code=redemption_model.coupon_code,
            redeemed_at=redemption_model.redeemed_at,
            discount_applied=redemption_model.discount_applied,
            extra_data=redemption_model.extra_data
        )

    async def get_coupon_usage_stats(self, code: str) -> Optional[dict]:
        """Get usage statistics for a coupon"""
        result = await self.db.execute(
            select(CouponModel).where(CouponModel.code == code.upper())
        )
        coupon_model = result.scalar_one_or_none()

        if not coupon_model:
            return None

        count_result = await self.db.execute(
            select(CouponRedemptionModel).where(
                CouponRedemptionModel.coupon_code == code.upper()
            )
        )
        redemptions_count = len(count_result.scalars().all())

        return {
            "code": coupon_model.code,
            "current_redemptions": coupon_model.current_redemptions,
            "max_redemptions": coupon_model.max_redemptions,
            "redemptions_remaining": (
                coupon_model.max_redemptions - coupon_model.current_redemptions
                if coupon_model.max_redemptions
                else None
            ),
            "active": coupon_model.active,
            "valid_from": coupon_model.valid_from.isoformat(),
            "valid_until": coupon_model.valid_until.isoformat() if coupon_model.valid_until else None
        }

    def _model_to_dataclass(self, model: CouponModel) -> Coupon:
        """Convert SQLAlchemy model to dataclass"""
        return Coupon(
            id=str(model.id),
            code=model.code,
            discount_type=DiscountType(model.discount_type),
            discount_value=model.discount_value,
            max_redemptions=model.max_redemptions,
            current_redemptions=model.current_redemptions,
            valid_from=model.valid_from,
            valid_until=model.valid_until,
            active=model.active,
            created_at=model.created_at,
            extra_data=model.extra_data
        )

    def _generate_discount_preview(self, coupon: Coupon) -> dict:
        """Generate a preview of the discount to be applied"""
        description = format_discount_description(coupon)

        preview = {
            "description": description,
            "discount_type": coupon.discount_type.value,
            "discount_value": coupon.discount_value
        }

        if coupon.discount_type == DiscountType.TRIAL_EXTENSION:
            trial_end = calculate_trial_end_date(0, coupon.discount_value)
            preview["trial_end_date"] = trial_end.isoformat()
            preview["total_trial_days"] = 0 + coupon.discount_value

        return preview

    def _calculate_discount_applied(
        self,
        coupon: Coupon,
        base_trial_days: int
    ) -> dict:
        """Calculate the actual discount that will be applied"""
        discount = {
            "type": coupon.discount_type.value,
            "value": coupon.discount_value,
            "description": format_discount_description(coupon)
        }

        if coupon.discount_type == DiscountType.TRIAL_EXTENSION:
            total_trial_days = base_trial_days + coupon.discount_value
            trial_end = calculate_trial_end_date(base_trial_days, coupon.discount_value)

            discount["base_trial_days"] = base_trial_days
            discount["extension_days"] = coupon.discount_value
            discount["total_trial_days"] = total_trial_days
            discount["trial_end_date"] = trial_end.isoformat()

        elif coupon.discount_type == DiscountType.PERCENTAGE:
            discount["percentage_off"] = coupon.discount_value

        elif coupon.discount_type == DiscountType.FIXED_AMOUNT:
            discount["amount_off_cents"] = coupon.discount_value
            discount["amount_off_euros"] = coupon.discount_value / 100

        return discount
