# Copyright (c) 2025 Patrick Motsch
# All rights reserved.
"""Subscription models: SubscriptionPlan (catalog), MandateSubscription (instance per mandate),
StripePlanPrice (persisted Stripe IDs per plan).

State Machine: see wiki/concepts/Subscription-State-Machine.md
"""

from typing import Dict, List, Optional
from enum import Enum
from datetime import datetime, timezone
from pydantic import BaseModel, Field
from modules.datamodels.datamodelBase import PowerOnModel
from modules.shared.i18nRegistry import i18nModel, t
import uuid


class SubscriptionStatusEnum(str, Enum):
    """Lifecycle status of a mandate subscription.
    See wiki/concepts/Subscription-State-Machine.md for transition rules."""
    PENDING = "PENDING"
    SCHEDULED = "SCHEDULED"
    TRIALING = "TRIALING"
    ACTIVE = "ACTIVE"
    PAST_DUE = "PAST_DUE"
    EXPIRED = "EXPIRED"


TERMINAL_STATUSES = {SubscriptionStatusEnum.EXPIRED}
OPERATIVE_STATUSES = {SubscriptionStatusEnum.ACTIVE, SubscriptionStatusEnum.TRIALING, SubscriptionStatusEnum.PAST_DUE}

ALLOWED_TRANSITIONS = {
    (SubscriptionStatusEnum.PENDING, SubscriptionStatusEnum.ACTIVE),
    (SubscriptionStatusEnum.PENDING, SubscriptionStatusEnum.TRIALING),
    (SubscriptionStatusEnum.PENDING, SubscriptionStatusEnum.SCHEDULED),
    (SubscriptionStatusEnum.PENDING, SubscriptionStatusEnum.EXPIRED),
    (SubscriptionStatusEnum.SCHEDULED, SubscriptionStatusEnum.ACTIVE),
    (SubscriptionStatusEnum.SCHEDULED, SubscriptionStatusEnum.EXPIRED),
    (SubscriptionStatusEnum.TRIALING, SubscriptionStatusEnum.EXPIRED),
    (SubscriptionStatusEnum.ACTIVE, SubscriptionStatusEnum.PAST_DUE),
    (SubscriptionStatusEnum.ACTIVE, SubscriptionStatusEnum.EXPIRED),
    (SubscriptionStatusEnum.PAST_DUE, SubscriptionStatusEnum.ACTIVE),
    (SubscriptionStatusEnum.PAST_DUE, SubscriptionStatusEnum.EXPIRED),
}


class BillingPeriodEnum(str, Enum):
    """Recurring billing interval."""
    MONTHLY = "MONTHLY"
    YEARLY = "YEARLY"
    NONE = "NONE"


# ============================================================================
# Catalog: SubscriptionPlan (static, in-memory)
# ============================================================================

@i18nModel("Abonnement-Plan")
class SubscriptionPlan(BaseModel):
    """Plan-Definition (Katalog). Nicht pro Mandat gespeichert — statisch."""
    planKey: str = Field(
        ...,
        description="Unique plan identifier",
        json_schema_extra={"label": "Plan"},
    )
    selectableByUser: bool = Field(
        default=True,
        description="Whether users can choose this plan in the UI",
        json_schema_extra={"label": "Waehlbar"},
    )

    title: str = Field(
        default="",
        description="Plan title (i18n key)",
        json_schema_extra={"label": "Titel"},
    )
    description: str = Field(
        default="",
        description="Plan description (i18n key)",
        json_schema_extra={"label": "Beschreibung"},
    )

    currency: str = Field(
        default="CHF",
        description="Billing currency",
        json_schema_extra={"label": "Waehrung"},
    )
    billingPeriod: BillingPeriodEnum = Field(
        default=BillingPeriodEnum.MONTHLY,
        description="Recurring interval",
        json_schema_extra={"label": "Abrechnungszeitraum"},
    )
    pricePerUserCHF: float = Field(
        default=0.0,
        description="Price per active user per period",
        json_schema_extra={"label": "Preis pro User (CHF)"},
    )
    pricePerFeatureInstanceCHF: float = Field(
        default=0.0,
        description="Price per additional module beyond included (monthly, CHF)",
        json_schema_extra={"label": "Preis pro Modul (CHF)"},
    )
    autoRenew: bool = Field(
        default=True,
        description="Stripe renews automatically at period end",
        json_schema_extra={"label": "Auto-Verlaengerung"},
    )

    maxUsers: Optional[int] = Field(
        None,
        description="Hard cap on active users (None = unlimited)",
        json_schema_extra={"label": "Max. Benutzer"},
    )
    maxFeatureInstances: Optional[int] = Field(
        None,
        description="Hard cap on active modules (None = unlimited)",
        json_schema_extra={"label": "Max. Module"},
    )
    includedModules: int = Field(
        default=0,
        description="Number of modules included in plan at no extra charge",
        json_schema_extra={"label": "Inkl. Module"},
    )
    trialDays: Optional[int] = Field(
        None,
        description="Trial duration in days (only for trial plans)",
        json_schema_extra={"label": "Probentage"},
    )
    maxDataVolumeMB: Optional[int] = Field(
        None,
        description="Soft-limit for data volume in MB per mandate (None = unlimited)",
        json_schema_extra={"label": "Datenvolumen (MB)"},
    )
    budgetAiCHF: float = Field(
        default=0.0,
        description="AI budget (CHF) total per billing period (users * budgetAiPerUserCHF at activation)",
        json_schema_extra={"label": "AI-Budget (CHF)"},
    )
    budgetAiPerUserCHF: float = Field(
        default=0.0,
        description="AI budget per user per month (CHF). Total = users * this value.",
        json_schema_extra={"label": "AI-Budget pro User (CHF)"},
    )
    successorPlanKey: Optional[str] = Field(
        None,
        description="Plan to transition to when trial ends",
        json_schema_extra={"label": "Nachfolge-Plan"},
    )


# ============================================================================
# Stripe Price mapping (persisted in DB, auto-created at bootstrap)
# ============================================================================

@i18nModel("Stripe-Planpreise")
class StripePlanPrice(BaseModel):
    """Persistierte Zuordnung planKey zu Stripe Product/Price IDs."""
    id: str = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="Primary key",
        json_schema_extra={"label": "ID"},
    )
    planKey: str = Field(
        ...,
        description="Reference to SubscriptionPlan.planKey",
        json_schema_extra={"label": "Plan"},
    )
    stripeProductId: str = Field(
        "",
        description="Legacy single-product ID (unused)",
        json_schema_extra={"label": "Stripe-Produkt-ID (Legacy)"},
    )
    stripeProductIdUsers: Optional[str] = Field(
        None,
        description="Stripe Product ID for user licenses",
        json_schema_extra={"label": "Produkt (User)"},
    )
    stripeProductIdInstances: Optional[str] = Field(
        None,
        description="Stripe Product ID for modules",
        json_schema_extra={"label": "Produkt (Module)"},
    )
    stripePriceIdUsers: Optional[str] = Field(
        None,
        description="Stripe Price ID for user-seat line item",
        json_schema_extra={"label": "Preis-ID (User)"},
    )
    stripePriceIdInstances: Optional[str] = Field(
        None,
        description="Stripe Price ID for module line item",
        json_schema_extra={"label": "Preis-ID (Module)"},
    )


# ============================================================================
# Instance: MandateSubscription
# ============================================================================

@i18nModel("Mandanten-Abonnement")
class MandateSubscription(PowerOnModel):
    """Abonnement-Instanz gebunden an einen Mandanten."""
    id: str = Field(
        default_factory=lambda: str(uuid.uuid4()),
        description="Primary key",
        json_schema_extra={"label": "ID"},
    )
    mandateId: str = Field(
        ...,
        description="Foreign key to Mandate",
        json_schema_extra={"label": "Mandanten-ID"},
    )
    planKey: str = Field(
        ...,
        description="Reference to SubscriptionPlan.planKey",
        json_schema_extra={"label": "Plan"},
    )

    status: SubscriptionStatusEnum = Field(
        default=SubscriptionStatusEnum.PENDING,
        description="Current lifecycle status",
        json_schema_extra={"label": "Status"},
    )
    recurring: bool = Field(
        default=True,
        description="True: auto-renews at period end. False: expires at period end (gekuendigt).",
        json_schema_extra={"label": "Wiederkehrend"},
    )

    startedAt: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="Record creation timestamp",
        json_schema_extra={"label": "Gestartet"},
    )
    effectiveFrom: Optional[datetime] = Field(
        None,
        description="When this subscription becomes operative. None = immediate. Set for SCHEDULED subs.",
        json_schema_extra={"label": "Wirksam ab"},
    )
    endedAt: Optional[datetime] = Field(
        None,
        description="When subscription ended (terminal)",
        json_schema_extra={"label": "Beendet"},
    )
    currentPeriodStart: Optional[datetime] = Field(
        None,
        description="Current billing period start (synced from Stripe)",
        json_schema_extra={"label": "Periodenbeginn"},
    )
    currentPeriodEnd: Optional[datetime] = Field(
        None,
        description="Current billing period end (synced from Stripe)",
        json_schema_extra={"label": "Periodenende"},
    )
    trialEndsAt: Optional[datetime] = Field(
        None,
        description="Trial expiry timestamp",
        json_schema_extra={"label": "Trial endet"},
    )

    snapshotPricePerUserCHF: float = Field(
        default=0.0,
        description="Price snapshot at activation (for invoice history)",
        json_schema_extra={"label": "Preis/User (CHF)"},
    )
    snapshotPricePerInstanceCHF: float = Field(
        default=0.0,
        description="Price snapshot at activation (per additional module)",
        json_schema_extra={"label": "Preis/Modul (CHF)"},
    )

    stripeSubscriptionId: Optional[str] = Field(
        None,
        description="Stripe Subscription ID (sub_xxx)",
        json_schema_extra={"label": "Stripe-Abonnement-ID"},
    )
    stripeItemIdUsers: Optional[str] = Field(
        None,
        description="Stripe Subscription Item ID for user seats",
        json_schema_extra={"label": "Stripe-Item (User)"},
    )
    stripeItemIdInstances: Optional[str] = Field(
        None,
        description="Stripe Subscription Item ID for feature instances",
        json_schema_extra={"label": "Stripe-Item (Instanzen)"},
    )


# ============================================================================
# Built-in plan catalog (static, no env dependency)
# ============================================================================

BUILTIN_PLANS: Dict[str, SubscriptionPlan] = {
    "ROOT": SubscriptionPlan(
        planKey="ROOT",
        selectableByUser=False,
        title=t("Root (System)"),
        description=t("Interner Systemplan — keine Verrechnung."),
        billingPeriod=BillingPeriodEnum.NONE,
        autoRenew=False,
        maxUsers=None,
        maxFeatureInstances=None,
        includedModules=0,
        maxDataVolumeMB=None,
        budgetAiCHF=0.0,
        budgetAiPerUserCHF=0.0,
    ),
    "TRIAL_14D": SubscriptionPlan(
        planKey="TRIAL_14D",
        selectableByUser=False,
        title=t("Gratis-Testphase (14 Tage)"),
        description=t("14 Tage kostenlos testen — 1 User, 2 Module inklusive, CHF 25 AI-Budget."),
        billingPeriod=BillingPeriodEnum.NONE,
        autoRenew=False,
        maxUsers=1,
        maxFeatureInstances=2,
        includedModules=2,
        trialDays=14,
        maxDataVolumeMB=1024,
        budgetAiCHF=25.0,
        budgetAiPerUserCHF=25.0,
        successorPlanKey="STARTER_MONTHLY",
    ),
    "STARTER_MONTHLY": SubscriptionPlan(
        planKey="STARTER_MONTHLY",
        selectableByUser=True,
        title=t("Starter (Monatlich)"),
        description=t("CHF 69 pro User/Monat. 2 Module inklusive, CHF 25 AI-Budget pro User."),
        billingPeriod=BillingPeriodEnum.MONTHLY,
        pricePerUserCHF=69.0,
        pricePerFeatureInstanceCHF=39.0,
        maxUsers=None,
        includedModules=2,
        maxDataVolumeMB=1024,
        budgetAiCHF=0.0,
        budgetAiPerUserCHF=25.0,
    ),
    "STARTER_YEARLY": SubscriptionPlan(
        planKey="STARTER_YEARLY",
        selectableByUser=True,
        title=t("Starter (Jaehrlich)"),
        description=t("CHF 690 pro User/Jahr (-17%). 2 Module inklusive, CHF 25 AI-Budget pro User/Monat."),
        billingPeriod=BillingPeriodEnum.YEARLY,
        pricePerUserCHF=690.0,
        pricePerFeatureInstanceCHF=39.0,
        maxUsers=None,
        includedModules=2,
        maxDataVolumeMB=1024,
        budgetAiCHF=0.0,
        budgetAiPerUserCHF=25.0,
    ),
    "PROFESSIONAL_MONTHLY": SubscriptionPlan(
        planKey="PROFESSIONAL_MONTHLY",
        selectableByUser=True,
        title=t("Professional (Monatlich)"),
        description=t("CHF 99 pro User/Monat. 5 Module inklusive, CHF 50 AI-Budget pro User."),
        billingPeriod=BillingPeriodEnum.MONTHLY,
        pricePerUserCHF=99.0,
        pricePerFeatureInstanceCHF=29.0,
        maxUsers=None,
        includedModules=5,
        maxDataVolumeMB=5120,
        budgetAiCHF=0.0,
        budgetAiPerUserCHF=50.0,
    ),
    "PROFESSIONAL_YEARLY": SubscriptionPlan(
        planKey="PROFESSIONAL_YEARLY",
        selectableByUser=True,
        title=t("Professional (Jaehrlich)"),
        description=t("CHF 990 pro User/Jahr (-17%). 5 Module inklusive, CHF 50 AI-Budget pro User/Monat."),
        billingPeriod=BillingPeriodEnum.YEARLY,
        pricePerUserCHF=990.0,
        pricePerFeatureInstanceCHF=29.0,
        maxUsers=None,
        includedModules=5,
        maxDataVolumeMB=5120,
        budgetAiCHF=0.0,
        budgetAiPerUserCHF=50.0,
    ),
    "MAX_MONTHLY": SubscriptionPlan(
        planKey="MAX_MONTHLY",
        selectableByUser=True,
        title=t("Max (Monatlich)"),
        description=t("CHF 145 pro User/Monat. 15 Module inklusive, CHF 100 AI-Budget pro User."),
        billingPeriod=BillingPeriodEnum.MONTHLY,
        pricePerUserCHF=145.0,
        pricePerFeatureInstanceCHF=19.0,
        maxUsers=None,
        includedModules=15,
        maxDataVolumeMB=25600,
        budgetAiCHF=0.0,
        budgetAiPerUserCHF=100.0,
    ),
    "MAX_YEARLY": SubscriptionPlan(
        planKey="MAX_YEARLY",
        selectableByUser=True,
        title=t("Max (Jaehrlich)"),
        description=t("CHF 1450 pro User/Jahr (-17%). 15 Module inklusive, CHF 100 AI-Budget pro User/Monat."),
        billingPeriod=BillingPeriodEnum.YEARLY,
        pricePerUserCHF=1450.0,
        pricePerFeatureInstanceCHF=19.0,
        maxUsers=None,
        includedModules=15,
        maxDataVolumeMB=25600,
        budgetAiCHF=0.0,
        budgetAiPerUserCHF=100.0,
    ),
}


def _getPlan(planKey: str) -> Optional[SubscriptionPlan]:
    """Resolve a plan by key from the built-in catalog."""
    return BUILTIN_PLANS.get(planKey)


def _getSelectablePlans() -> List[SubscriptionPlan]:
    """Return plans that users can choose in the UI."""
    return [p for p in BUILTIN_PLANS.values() if p.selectableByUser]
