"""
Centralized tier limits configuration loader.

Single source of truth for:
- Per-enrichment pricing (customer price, provider cost) - LEGACY/BYO tracking
- Tier monthly allocations and quotas
- Enrichment pack SKUs and pricing
- Rollover policy
- System constraints

Renamed from enrichment_pricing.py (2026-01-19) - legacy name from when
enrichment credits were the primary metered thing. Now we use BYO model.

This answers: "How much can they do?"
product_capabilities.yaml answers: "What can they do?"

Usage:
    from fmatch.saas.config.tier_limits import TIER_CONFIG, get_tier_config

    # Get all tier limits
    tier_limits = TIER_CONFIG.get_tier_limits()

    # Get specific tier config
    pro_config = get_tier_config("pro")
    monthly_rows = pro_config.monthly_rows

    # Get pack details
    starter_pack = TIER_CONFIG.get_pack("starter")
"""

from __future__ import annotations

import logging
from decimal import Decimal
from pathlib import Path
from typing import Any, Dict, List, Optional

import yaml
from pydantic import BaseModel, Field, validator

logger = logging.getLogger(__name__)

DEFAULT_TIER_ALIASES = {
    "enterprise": "unleashed",
    "scale": "unleashed",
}


# =============================================================================
# Configuration Models
# =============================================================================


class PricingConfig(BaseModel):
    """Per-enrichment pricing configuration (LEGACY - for BYO cost tracking)."""

    price_per_enrichment_usd: Decimal = Field(
        ..., description="Customer price per enrichment"
    )
    provider_cost_per_enrichment_usd: Decimal = Field(
        ..., description="Provider (PDL) cost per enrichment"
    )
    credits_per_enrichment: int = Field(
        ..., ge=1, description="Credits to charge per enrichment"
    )
    margin_percentage: float = Field(..., ge=0, le=100, description="Profit margin %")

    @property
    def margin_usd(self) -> Decimal:
        """Calculate margin per enrichment in USD."""
        return (
            self.price_per_enrichment_usd - self.provider_cost_per_enrichment_usd
        )


class TierPricingInfo(BaseModel):
    """Pricing information for a tier."""

    monthly_usd: int = Field(..., ge=0)
    annual_usd: int = Field(..., ge=0)


class StripeInfo(BaseModel):
    """Stripe price IDs for a tier."""

    price_id_monthly: str = Field(..., pattern=r"^price_[a-zA-Z0-9]+$")
    price_id_annual: str = Field(..., pattern=r"^price_[a-zA-Z0-9]+$")


class TierDisplayInfo(BaseModel):
    """Display strings for tier UI."""

    matches_description: str
    enrichment_description: Optional[str] = None  # Optional - shelved for V1
    popular: bool = False


class TierConfig(BaseModel):
    """Configuration for a subscription tier."""

    name: str
    tagline: Optional[str] = None
    monthly_enrichments: int = Field(default=0, ge=0)  # Optional - shelved for V1
    monthly_rows: int = Field(..., ge=1000)
    rows_per_run: int = Field(default=5000, ge=100)
    # AI Limits
    gremlin_enabled: bool = False
    gremlin_messages_per_day: int = Field(default=0, ge=0)
    ai_report_row_cap: int = Field(default=0, ge=0)
    # List Builder
    list_builder_monthly_rows: int = Field(..., ge=0)
    list_builder_daily_rows: int = Field(..., ge=0)
    list_builder_preview_rows: int = Field(..., ge=0)
    list_builder_max_columns: int = Field(..., ge=1)
    # Scheduling
    grace_percentage: float = Field(..., ge=0, le=1)
    max_schedules: int = Field(..., ge=0)
    allowed_frequencies: List[str]
    concurrent_jobs: int = Field(..., ge=1)
    # Matching
    candidate_cap: int = Field(..., ge=1)
    require_b2c: bool
    allow_advanced_algos: bool
    # Salesforce Limits
    soql_runs_per_month: Optional[int] = None
    salesforce_orgs: int = Field(..., ge=1)
    # SFDC Account Matcher - "burst credit" model for Free tier
    match_credits_lifetime: Optional[int] = None  # null = unlimited
    match_credits_preview_rows: int = Field(default=50, ge=0)  # Preview-only after exhausted
    # Import Wizard - separate validate (unlimited) from commit (capped)
    import_wizard_commits_monthly: Optional[int] = None  # null = unlimited
    import_wizard_commit_row_cap: Optional[int] = None  # null = unlimited
    # Users
    users: int = Field(..., ge=1)
    # Webhook Limits
    max_sheet_webhooks: int = Field(default=1, ge=0)
    webhook_rows_per_month: int = Field(default=1000, ge=0)
    webhook_history_retention_days: int = Field(default=1, ge=1)
    # BYO Enrichment
    byo_enrichment_enabled: bool = False
    byo_bundled: bool = False
    byo_providers: List[str] = Field(default_factory=list)
    byo_max_rows_per_job: Optional[int] = Field(default=None, ge=0)
    byo_concurrent_enrichment_jobs: Optional[int] = Field(default=None, ge=0)
    byo_daily_enrichment_rows: Optional[int] = Field(default=None, ge=0)
    byo_monthly_enrichment_rows: Optional[int] = None
    byo_scheduled_enrichment: bool = False
    byo_scheduled_frequencies: Optional[List[str]] = None
    byo_priority_queue: bool = False
    # Pricing/Display
    pricing: TierPricingInfo
    stripe: Optional[StripeInfo] = None
    display: TierDisplayInfo

    def to_tier_limits_dict(self) -> Dict[str, Any]:
        """Convert to TIER_LIMITS dict format for backwards compatibility."""
        return {
            "monthly_rows": self.monthly_rows,
            "rows_per_run": self.rows_per_run,
            "grace_percentage": self.grace_percentage,
            "enrichment_credits": self.monthly_enrichments,
            "gremlin_enabled": self.gremlin_enabled,
            "gremlin_messages_per_day": self.gremlin_messages_per_day,
            "ai_report_row_cap": self.ai_report_row_cap,
            "list_builder_monthly_rows": self.list_builder_monthly_rows,
            "list_builder_daily_rows": self.list_builder_daily_rows,
            "list_builder_preview_rows": self.list_builder_preview_rows,
            "list_builder_max_columns": self.list_builder_max_columns,
            "max_schedules": self.max_schedules,
            "allowed_frequencies": self.allowed_frequencies,
            "concurrent_jobs": self.concurrent_jobs,
            "candidate_cap": self.candidate_cap,
            "require_b2c": self.require_b2c,
            "allow_advanced_algos": self.allow_advanced_algos,
            "soql_runs_per_month": self.soql_runs_per_month,
            "salesforce_orgs": self.salesforce_orgs,
            # New Salesforce gating limits (v1.2.0)
            "match_credits_lifetime": self.match_credits_lifetime,
            "match_credits_preview_rows": self.match_credits_preview_rows,
            "import_wizard_commits_monthly": self.import_wizard_commits_monthly,
            "import_wizard_commit_row_cap": self.import_wizard_commit_row_cap,
            # Users
            "users": self.users,
            # Webhooks
            "max_sheet_webhooks": self.max_sheet_webhooks,
            "webhook_rows_per_month": self.webhook_rows_per_month,
            "webhook_history_retention_days": self.webhook_history_retention_days,
            # BYO
            "byo_enrichment_enabled": self.byo_enrichment_enabled,
            "byo_bundled": self.byo_bundled,
            "byo_providers": list(self.byo_providers or []),
            "byo_max_rows_per_job": self.byo_max_rows_per_job,
            "byo_concurrent_enrichment_jobs": self.byo_concurrent_enrichment_jobs,
            "byo_daily_enrichment_rows": self.byo_daily_enrichment_rows,
            "byo_monthly_enrichment_rows": self.byo_monthly_enrichment_rows,
            "byo_scheduled_enrichment": self.byo_scheduled_enrichment,
            "byo_scheduled_frequencies": list(self.byo_scheduled_frequencies or [])
            if self.byo_scheduled_frequencies
            else None,
            "byo_priority_queue": self.byo_priority_queue,
        }


class AddonPricingInfo(BaseModel):
    """Pricing information for an add-on."""

    monthly_usd: int = Field(..., ge=0)
    annual_usd: int = Field(..., ge=0)


class AddonStripeInfo(BaseModel):
    """Stripe price IDs for an add-on."""

    price_id_monthly: str = Field(..., pattern=r"^price_[a-zA-Z0-9]+$")
    price_id_annual: str = Field(..., pattern=r"^price_[a-zA-Z0-9]+$")


class AddonDisplayInfo(BaseModel):
    """Display strings for add-on UI."""

    tagline: Optional[str] = None
    cta: Optional[str] = None
    best_for: Optional[str] = None


class AddonLimitConfig(BaseModel):
    """Per-tier limits for an add-on."""

    max_rows_per_job: Optional[int] = None
    concurrent_enrichment_jobs: Optional[int] = None
    daily_enrichment_rows: Optional[int] = None
    monthly_enrichment_rows: Optional[int] = None
    scheduled_enrichment: Optional[bool] = None
    scheduled_frequencies: Optional[List[str]] = None


class AddonConfig(BaseModel):
    """Configuration for a purchasable add-on."""

    name: str
    description: Optional[str] = None
    pricing: AddonPricingInfo
    stripe: AddonStripeInfo
    available_tiers: List[str] = Field(default_factory=list)
    providers: List[str] = Field(default_factory=list)
    limits_by_tier: Dict[str, AddonLimitConfig] = Field(default_factory=dict)
    display: Optional[AddonDisplayInfo] = None


class EnrichmentPackConfig(BaseModel):
    """Configuration for a prepaid enrichment pack."""

    name: str
    enrichments: int = Field(..., ge=1)
    price_usd: int = Field(..., ge=1)
    price_per_enrichment: Decimal
    stripe_price_id: str = Field(..., pattern=r"^price_[a-zA-Z0-9]+$")
    description: str
    never_expires: bool = True
    popular: bool = False


class RolloverConfig(BaseModel):
    """Enrichment rollover policy configuration."""

    enabled: bool = True
    max_multiplier: float = Field(..., ge=0, le=2)
    expiry_days: int = Field(..., ge=1)
    description: str


class ConstraintsConfig(BaseModel):
    """System constraints and guardrails."""

    min_credits_per_request: int = Field(..., ge=1)
    max_account_daily_spend_usd: int = Field(..., ge=100)
    max_global_daily_spend_usd: int = Field(..., ge=1000)
    reservation_ttl_seconds: int = Field(..., ge=60)
    idempotency_ttl_hours: int = Field(..., ge=1)


class StripeIntegrationConfig(BaseModel):
    """Stripe integration settings."""

    webhook_secret_env: str
    api_key_env: str
    pack_metadata_key: str


class FeaturesConfig(BaseModel):
    """Feature flags for enrichment system."""

    enable_rollover: bool = True
    enable_packs: bool = True
    enable_margin_alerts: bool = True
    margin_alert_threshold: float = Field(..., ge=0, le=1)
    enable_byo_addon: bool = True  # BYO Provider Locker add-on


class TierLimitsConfig(BaseModel):
    """Complete tier limits configuration."""

    pricing: PricingConfig
    tiers: Dict[str, TierConfig]
    enrichment_packs: Dict[str, EnrichmentPackConfig]
    addons: Dict[str, AddonConfig] = Field(default_factory=dict)
    rollover: RolloverConfig
    constraints: ConstraintsConfig
    stripe: StripeIntegrationConfig
    features: FeaturesConfig
    aliases: Dict[str, str] = Field(default_factory=dict)

    @validator("tiers")
    def validate_tiers(cls, v):
        """Ensure required tiers exist."""
        required = {"free", "pro", "unleashed"}
        if not required.issubset(v.keys()):
            missing = required - set(v.keys())
            raise ValueError(f"Missing required tiers: {missing}")
        return v

    @validator("aliases")
    def validate_aliases(cls, v, values):
        """Ensure aliases point to known tiers."""
        tiers = values.get("tiers") or {}
        for alias, target in (v or {}).items():
            if target not in tiers:
                raise ValueError(
                    f"Alias '{alias}' points to unknown tier '{target}'."
                )
        return v

    def get_tier_limits(self) -> Dict[str, Dict[str, Any]]:
        """
        Get TIER_LIMITS dict for backwards compatibility.

        Returns dict matching the format expected by existing code:
            {
                "free": {...},
                "pro": {...},
                "scale": {...},
            }
        """
        limits = {}
        for tier_name, tier_config in self.tiers.items():
            # Skip aliases and deprecated tiers
            if tier_name.startswith("_"):
                continue
            limits[tier_name] = tier_config.to_tier_limits_dict()

        for alias, target in (self.aliases or {}).items():
            resolved = self.resolve_tier_alias(target)
            if resolved in limits:
                limits[alias] = limits[resolved].copy()

        return limits

    def resolve_tier_alias(self, tier_name: str) -> str:
        """Resolve alias chains to a canonical tier."""
        value = (tier_name or "").strip().lower()
        seen = set()
        while value in (self.aliases or {}) and value not in seen:
            seen.add(value)
            value = (self.aliases or {}).get(value, value)
        return value

    def get_tier_config(self, tier_name: str) -> TierConfig:
        """Get configuration for a specific tier."""
        resolved = self.resolve_tier_alias(tier_name)
        if resolved not in self.tiers:
            raise ValueError(
                f"Unknown tier: {tier_name}. Available: {list(self.tiers.keys())}"
            )
        return self.tiers[resolved]

    def get_pack(self, pack_name: str) -> EnrichmentPackConfig:
        """Get configuration for a specific pack."""
        if pack_name not in self.enrichment_packs:
            raise ValueError(
                f"Unknown pack: {pack_name}. Available: {list(self.enrichment_packs.keys())}"
            )
        return self.enrichment_packs[pack_name]

    def get_addon(self, addon_name: str) -> AddonConfig:
        """Get configuration for a specific add-on."""
        if addon_name not in self.addons:
            raise ValueError(
                f"Unknown addon: {addon_name}. Available: {list(self.addons.keys())}"
            )
        return self.addons[addon_name]

    def get_pack_by_stripe_price_id(
        self, price_id: str
    ) -> Optional[EnrichmentPackConfig]:
        """Find pack by Stripe price ID."""
        for pack in self.enrichment_packs.values():
            if pack.stripe_price_id == price_id:
                return pack
        return None

    def get_addon_by_stripe_price_id(self, price_id: str) -> Optional[AddonConfig]:
        """Find add-on by Stripe price ID."""
        for addon in self.addons.values():
            stripe = addon.stripe
            if not stripe:
                continue
            if price_id in (stripe.price_id_monthly, stripe.price_id_annual):
                return addon
        return None

    def get_addon_limits(
        self, addon_name: str, tier_name: str
    ) -> Optional[AddonLimitConfig]:
        """Return per-tier limits for an add-on."""
        addon = self.get_addon(addon_name)
        return addon.limits_by_tier.get(tier_name)

    def validate_margin(self) -> bool:
        """Check if margin meets threshold."""
        if not self.features.enable_margin_alerts:
            return True

        margin_ratio = (
            self.pricing.margin_usd / self.pricing.price_per_enrichment_usd
        )
        return float(margin_ratio) >= self.features.margin_alert_threshold


# =============================================================================
# Config Loader
# =============================================================================


def load_tier_limits_config(
    config_path: Optional[Path] = None,
) -> TierLimitsConfig:
    """
    Load tier limits configuration from YAML file.

    Args:
        config_path: Path to YAML config file. If None, uses default location.

    Returns:
        Validated TierLimitsConfig instance

    Raises:
        FileNotFoundError: If config file doesn't exist
        ValueError: If config validation fails
    """
    if config_path is None:
        # Default location: config/tier_limits.yaml from repo root
        repo_root = Path(__file__).parent.parent.parent.parent.parent
        config_path = repo_root / "config" / "tier_limits.yaml"

        # Fallback to legacy name for backwards compatibility
        if not config_path.exists():
            legacy_path = repo_root / "config" / "enrichment_pricing.yaml"
            if legacy_path.exists():
                logger.warning(
                    f"Using legacy config path: {legacy_path}\n"
                    f"Please rename to config/tier_limits.yaml"
                )
                config_path = legacy_path

    if not config_path.exists():
        raise FileNotFoundError(
            f"Tier limits config not found at: {config_path}\n"
            f"Please create config/tier_limits.yaml in the repo root."
        )

    logger.info(f"Loading tier limits config from: {config_path}")

    with open(config_path) as f:
        data = yaml.safe_load(f)

    try:
        config = TierLimitsConfig(**data)
        logger.info(
            f"Loaded tier limits config: "
            f"{len(config.tiers)} tiers, {len(config.enrichment_packs)} packs"
        )

        # Validate margin if enabled
        if not config.validate_margin():
            logger.warning(
                f"Margin ({config.pricing.margin_percentage:.1f}%) "
                f"below threshold ({config.features.margin_alert_threshold * 100:.1f}%)"
            )

        return config

    except Exception as e:
        logger.error(f"Failed to validate tier limits config: {e}")
        raise ValueError(f"Invalid tier limits config: {e}") from e


# =============================================================================
# Global Instance
# =============================================================================

# Load config at module import time
try:
    TIER_CONFIG = load_tier_limits_config()
except Exception as e:
    logger.error(
        f"Failed to load tier limits config at startup: {e}\n"
        f"Using minimal fallback configuration."
    )
    # Fallback to minimal config to prevent startup failure
    # This allows the app to start even if config file is missing
    # Production deployments should fail loudly instead
    TIER_CONFIG = None

# Backwards compatibility aliases
ENRICHMENT_CONFIG = TIER_CONFIG


def get_tier_config(tier_name: str) -> TierConfig:
    """
    Convenience function to get tier configuration.

    Args:
        tier_name: Name of tier (free, pro, scale, unleashed)

    Returns:
        TierConfig for the specified tier

    Raises:
        ValueError: If tier doesn't exist or config not loaded
    """
    if TIER_CONFIG is None:
        raise ValueError(
            "Tier limits config not loaded. Check config/tier_limits.yaml"
        )
    return TIER_CONFIG.get_tier_config(tier_name)


def normalize_tier_name(tier_name: Optional[str]) -> str:
    """Normalize tier names and apply alias mappings."""
    value = str(tier_name or "").strip().lower()
    if not value:
        return "free"
    if TIER_CONFIG is None:
        return DEFAULT_TIER_ALIASES.get(value, value)
    return TIER_CONFIG.resolve_tier_alias(value)


def get_tier_limits() -> Dict[str, Dict[str, Any]]:
    """
    Get TIER_LIMITS dict for backwards compatibility.

    Returns:
        Dict matching existing TIER_LIMITS format

    Raises:
        ValueError: If config not loaded
    """
    if TIER_CONFIG is None:
        raise ValueError(
            "Tier limits config not loaded. Check config/tier_limits.yaml"
        )
    return TIER_CONFIG.get_tier_limits()
