from __future__ import annotations
import logging
from datetime import datetime, timezone
from typing import List, Optional

from fastapi import HTTPException, status
from sqlalchemy import select, update
from sqlalchemy.orm import Session

from src.apps.plan_management.crud import (
    get_plan,
    create_plan as crud_create_plan,
    get_merchant_count_for_plan,
    get_affected_merchant_ids,
    soft_delete_plan,
    check_plan_name_unique,
)
from src.apps.plan_management.schemas.plan_schemas import (
    PlanCreate,
    PlanUpdate,
)
from src.apps.plan_management.models.plan import Plan

logger = logging.getLogger(__name__)


def _build_plan_detail(db: Session, plan: Plan) -> dict:
    count = get_merchant_count_for_plan(db, plan)
    pricing = plan.pricing_template
    feature = plan.feature_plan

    pricing_resp = {
        "id": pricing.id,
        "name": pricing.name,
        "description": pricing.description,
        "pricing_type": pricing.pricing_type,
        "billing_cycle": pricing.billing_cycle,
        "monthly_fee_cents": pricing.monthly_fee_cents,
        "setup_fee_cents": pricing.setup_fee_cents,
        "rates": [
            {
                "id": r.id,
                "transaction_type": r.transaction_type,
                "rate_percentage": r.rate_percentage,
                "fixed_fee_cents": r.fixed_fee_cents,
                "tier_name": r.tier_name,
                "interchange_basis_points": r.interchange_basis_points,
                "markup_basis_points": r.markup_basis_points,
            }
            for r in (pricing.rates or [])
        ],
    }
    features_resp = {
        "id": feature.id,
        "name": feature.name,
        "description": feature.description,
        "is_active": feature.is_active,
        "items": [
            {"feature_slug": i.feature_slug, "is_enabled": i.is_enabled}
            for i in (feature.items or [])
        ],
    }
    return {
        "id": plan.id,
        "name": plan.name,
        "description": plan.description,
        "is_active": plan.is_active,
        "created_by_user_id": plan.created_by_user_id,
        "created_at": plan.created_at,
        "updated_at": plan.updated_at,
        "pricing": pricing_resp,
        "features": features_resp,
        "assigned_merchant_count": count,
    }


class PlanService:

    @staticmethod
    def create_plan(db: Session, data: PlanCreate, created_by_user_id: int) -> dict:
        import src.apps.pricing_control.crud as pricing_crud
        import src.apps.feature_control.crud as feature_crud
        from src.apps.pricing_control.schemas.pricing_schemas import (
            PricingTemplateCreate,
            PricingTemplateRateCreate,
        )
        from src.apps.feature_control.schemas.feature_schemas import (
            FeaturePlanCreate,
            FeaturePlanItemCreate,
        )

        check_plan_name_unique(db, data.name)

        template = pricing_crud.create_template(
            db,
            PricingTemplateCreate(
                name=data.name,
                description=data.description,
                pricing_type="flat_rate",
                billing_cycle=data.pricing.billing_cycle,
                monthly_fee_cents=data.pricing.monthly_fee_cents,
                setup_fee_cents=data.pricing.setup_fee_cents,
                rates=[
                    PricingTemplateRateCreate(
                        transaction_type=r.transaction_type,
                        rate_percentage=r.rate_percentage,
                        fixed_fee_cents=r.fixed_fee_cents,
                        tier_name=r.tier_name,
                        interchange_basis_points=r.interchange_basis_points,
                        markup_basis_points=r.markup_basis_points,
                    )
                    for r in data.pricing.rates
                ],
            ),
            created_by_user_id=created_by_user_id,
        )

        feature_plan = feature_crud.create_plan(
            db,
            FeaturePlanCreate(
                name=data.name,
                description=data.description,
                items=[
                    FeaturePlanItemCreate(
                        feature_slug=i.feature_slug,
                        is_enabled=i.is_enabled,
                    )
                    for i in data.features.items
                ],
            ),
            created_by=created_by_user_id,
        )

        plan = crud_create_plan(
            db,
            name=data.name,
            description=data.description,
            is_active=data.is_active,
            pricing_template_id=template.id,
            feature_plan_id=feature_plan.id,
            created_by_user_id=created_by_user_id,
        )

        db.commit()
        db.refresh(plan)

        # Reload with relationships
        plan = get_plan(db, plan.id)
        return _build_plan_detail(db, plan)

    @staticmethod
    def update_plan(
        db: Session,
        plan_id: int,
        data: PlanUpdate,
        updated_by_user_id: int,
        redis_client,
    ) -> dict:
        import src.apps.pricing_control.crud as pricing_crud
        import src.apps.feature_control.crud as feature_crud
        from src.apps.feature_control.services import FeatureService
        from src.apps.merchants.models.merchant_billing_method import MerchantBillingMethod
        from src.apps.pricing_control.schemas.pricing_schemas import (
            PricingTemplateUpdate,
            PricingTemplateRateCreate,
        )
        from src.apps.feature_control.schemas.feature_schemas import (
            FeaturePlanUpdate,
            FeaturePlanItemCreate,
        )

        plan = get_plan(db, plan_id)
        if not plan:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Plan not found")

        if data.name is not None and data.name != plan.name:
            check_plan_name_unique(db, data.name, exclude_id=plan_id)
            plan.name = data.name
            # Sync name to pricing template (no unique constraint on PricingTemplate.name)
            plan.pricing_template.name = data.name
            # NOTE: FeaturePlan.name has a global unique constraint — do not sync to avoid
            # IntegrityError when another feature plan with the same name already exists.
            db.flush()

        if data.description is not None:
            plan.description = data.description
            db.flush()

        if data.is_active is not None:
            plan.is_active = data.is_active
            db.flush()

        if data.pricing is not None:
            old_template_id = plan.pricing_template_id
            affected_merchant_ids = get_affected_merchant_ids(db, old_template_id)

            new_template = pricing_crud.update_template(
                db,
                old_template_id,
                PricingTemplateUpdate(
                    name=plan.name,
                    monthly_fee_cents=data.pricing.monthly_fee_cents,
                    setup_fee_cents=data.pricing.setup_fee_cents,
                    rates=[
                        PricingTemplateRateCreate(
                            transaction_type=r.transaction_type,
                            rate_percentage=r.rate_percentage,
                            fixed_fee_cents=r.fixed_fee_cents,
                            tier_name=r.tier_name,
                            interchange_basis_points=r.interchange_basis_points,
                            markup_basis_points=r.markup_basis_points,
                        )
                        for r in data.pricing.rates
                    ],
                ),
                updated_by_user_id=updated_by_user_id,
            )

            plan.pricing_template_id = new_template.id
            db.flush()

            if affected_merchant_ids:
                pricing_crud.assign_template_to_merchants(
                    db,
                    new_template.id,
                    affected_merchant_ids,
                    effective_date=datetime.now(timezone.utc),
                    assigned_by_user_id=updated_by_user_id,
                    notes="Plan pricing update cascade",
                )
                db.execute(
                    update(MerchantBillingMethod)
                    .where(
                        MerchantBillingMethod.merchant_id.in_(affected_merchant_ids),
                        MerchantBillingMethod.is_active == True,
                        MerchantBillingMethod.deleted_at.is_(None),
                    )
                    .values(requires_revalidation=True)
                )

        if data.features is not None:
            feature_merchant_ids = feature_crud.get_active_merchant_ids_for_plan(
                db, plan.feature_plan_id
            )
            feature_crud.update_plan(
                db,
                plan.feature_plan_id,
                FeaturePlanUpdate(
                    name=plan.name,
                    items=[
                        FeaturePlanItemCreate(
                            feature_slug=i.feature_slug,
                            is_enabled=i.is_enabled,
                        )
                        for i in data.features.items
                    ],
                ),
            )
            if feature_merchant_ids and redis_client:
                FeatureService.invalidate_bulk(redis_client, feature_merchant_ids)

        db.commit()
        plan = get_plan(db, plan_id)
        return _build_plan_detail(db, plan)

    @staticmethod
    def assign_plan(
        db: Session,
        plan_id: int,
        merchant_id: int,
        assigned_by_user_id: int,
        notes: Optional[str],
        redis_client,
    ) -> dict:
        import src.apps.pricing_control.crud as pricing_crud
        import src.apps.feature_control.crud as feature_crud
        from src.apps.feature_control.services import FeatureService

        plan = get_plan(db, plan_id)
        if not plan:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Plan not found")
        if not plan.is_active:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Cannot assign an inactive plan",
            )

        from src.apps.merchants.models.merchant import Merchant
        merchant = db.execute(
            select(Merchant).where(Merchant.id == merchant_id, Merchant.deleted_at.is_(None))
        ).scalar_one_or_none()
        if not merchant:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Merchant not found")

        pricing_crud.assign_template_to_merchants(
            db,
            plan.pricing_template_id,
            [merchant_id],
            effective_date=datetime.now(timezone.utc),
            assigned_by_user_id=assigned_by_user_id,
            notes=notes,
        )

        feature_crud.assign_plan_to_merchants(
            db,
            plan.feature_plan_id,
            [merchant_id],
            assigned_by=assigned_by_user_id,
            notes=notes,
        )

        # Clear any per-merchant feature overrides so the plan becomes authoritative.
        # Admins can re-add targeted overrides after assignment if exceptions are needed.
        feature_crud.clear_overrides(db, merchant_id)

        if redis_client:
            FeatureService.invalidate(redis_client, merchant_id)
        db.commit()

        return {
            "plan_id": plan_id,
            "merchant_id": merchant_id,
            "message": f"Plan '{plan.name}' assigned to merchant {merchant_id} successfully.",
        }

    @staticmethod
    def get_plan_detail(db: Session, plan_id: int) -> dict:
        plan = get_plan(db, plan_id)
        if not plan:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Plan not found")
        return _build_plan_detail(db, plan)

    @staticmethod
    def delete_plan(db: Session, plan_id: int) -> None:
        plan = get_plan(db, plan_id)
        if not plan:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Plan not found")
        soft_delete_plan(db, plan)
        db.commit()
