# app/modules/marketplace/services/platform_signup_service.py
"""
Platform signup service.

Handles all database operations for the platform signup flow:
- Session management
- Vendor claiming
- Account creation
- Subscription setup
"""

import logging
import secrets
from datetime import UTC, datetime, timedelta
from dataclasses import dataclass

from sqlalchemy.orm import Session

from app.core.config import settings
from app.exceptions import (
    ConflictException,
    ResourceNotFoundException,
    ValidationException,
)
from app.modules.messaging.services.email_service import EmailService
from app.modules.marketplace.services.onboarding_service import OnboardingService
from app.modules.billing.services.stripe_service import stripe_service
from middleware.auth import AuthManager
from app.modules.tenancy.models import Company
from app.modules.billing.models import (
    SubscriptionStatus,
    TierCode,
    TIER_LIMITS,
    VendorSubscription,
)
from app.modules.tenancy.models import User
from app.modules.tenancy.models import Vendor, VendorUser, VendorUserType

logger = logging.getLogger(__name__)


# =============================================================================
# In-memory signup session storage
# In production, use Redis or database table
# =============================================================================

_signup_sessions: dict[str, dict] = {}


def _create_session_id() -> str:
    """Generate a secure session ID."""
    return secrets.token_urlsafe(32)


# =============================================================================
# Data Classes
# =============================================================================


@dataclass
class SignupSessionData:
    """Data stored in a signup session."""

    session_id: str
    step: str
    tier_code: str
    is_annual: bool
    created_at: str
    updated_at: str | None = None
    letzshop_slug: str | None = None
    letzshop_vendor_id: str | None = None
    vendor_name: str | None = None
    user_id: int | None = None
    vendor_id: int | None = None
    vendor_code: str | None = None
    stripe_customer_id: str | None = None
    setup_intent_id: str | None = None


@dataclass
class AccountCreationResult:
    """Result of account creation."""

    user_id: int
    vendor_id: int
    vendor_code: str
    stripe_customer_id: str


@dataclass
class SignupCompletionResult:
    """Result of signup completion."""

    success: bool
    vendor_code: str
    vendor_id: int
    redirect_url: str
    trial_ends_at: str
    access_token: str | None = None  # JWT token for automatic login


# =============================================================================
# Platform Signup Service
# =============================================================================


class PlatformSignupService:
    """Service for handling platform signup operations."""

    def __init__(self):
        self.auth_manager = AuthManager()

    # =========================================================================
    # Session Management
    # =========================================================================

    def create_session(self, tier_code: str, is_annual: bool) -> str:
        """
        Create a new signup session.

        Args:
            tier_code: The subscription tier code
            is_annual: Whether annual billing is selected

        Returns:
            The session ID

        Raises:
            ValidationException: If tier code is invalid
        """
        # Validate tier code
        try:
            tier = TierCode(tier_code)
        except ValueError:
            raise ValidationException(
                message=f"Invalid tier code: {tier_code}",
                field="tier_code",
            )

        session_id = _create_session_id()
        now = datetime.now(UTC).isoformat()

        _signup_sessions[session_id] = {
            "step": "tier_selected",
            "tier_code": tier.value,
            "is_annual": is_annual,
            "created_at": now,
            "updated_at": now,
        }

        logger.info(f"Created signup session {session_id} for tier {tier.value}")
        return session_id

    def get_session(self, session_id: str) -> dict | None:
        """Get a signup session by ID."""
        return _signup_sessions.get(session_id)

    def get_session_or_raise(self, session_id: str) -> dict:
        """
        Get a signup session or raise an exception.

        Raises:
            ResourceNotFoundException: If session not found
        """
        session = self.get_session(session_id)
        if not session:
            raise ResourceNotFoundException(
                resource_type="SignupSession",
                identifier=session_id,
            )
        return session

    def update_session(self, session_id: str, data: dict) -> None:
        """Update signup session data."""
        session = self.get_session_or_raise(session_id)
        session.update(data)
        session["updated_at"] = datetime.now(UTC).isoformat()
        _signup_sessions[session_id] = session

    def delete_session(self, session_id: str) -> None:
        """Delete a signup session."""
        _signup_sessions.pop(session_id, None)

    # =========================================================================
    # Vendor Claiming
    # =========================================================================

    def check_vendor_claimed(self, db: Session, letzshop_slug: str) -> bool:
        """Check if a Letzshop vendor is already claimed."""
        return (
            db.query(Vendor)
            .filter(
                Vendor.letzshop_vendor_slug == letzshop_slug,
                Vendor.is_active == True,
            )
            .first()
            is not None
        )

    def claim_vendor(
        self,
        db: Session,
        session_id: str,
        letzshop_slug: str,
        letzshop_vendor_id: str | None = None,
    ) -> str:
        """
        Claim a Letzshop vendor for signup.

        Args:
            db: Database session
            session_id: Signup session ID
            letzshop_slug: Letzshop vendor slug
            letzshop_vendor_id: Optional Letzshop vendor ID

        Returns:
            Generated vendor name

        Raises:
            ResourceNotFoundException: If session not found
            ConflictException: If vendor already claimed
        """
        session = self.get_session_or_raise(session_id)

        # Check if vendor is already claimed
        if self.check_vendor_claimed(db, letzshop_slug):
            raise ConflictException(
                message="This Letzshop vendor is already claimed",
            )

        # Generate vendor name from slug
        vendor_name = letzshop_slug.replace("-", " ").title()

        # Update session
        self.update_session(session_id, {
            "letzshop_slug": letzshop_slug,
            "letzshop_vendor_id": letzshop_vendor_id,
            "vendor_name": vendor_name,
            "step": "vendor_claimed",
        })

        logger.info(f"Claimed vendor {letzshop_slug} for session {session_id}")
        return vendor_name

    # =========================================================================
    # Account Creation
    # =========================================================================

    def check_email_exists(self, db: Session, email: str) -> bool:
        """Check if an email already exists."""
        return db.query(User).filter(User.email == email).first() is not None

    def generate_unique_username(self, db: Session, email: str) -> str:
        """Generate a unique username from email."""
        username = email.split("@")[0]
        base_username = username
        counter = 1
        while db.query(User).filter(User.username == username).first():
            username = f"{base_username}_{counter}"
            counter += 1
        return username

    def generate_unique_vendor_code(self, db: Session, company_name: str) -> str:
        """Generate a unique vendor code from company name."""
        vendor_code = company_name.upper().replace(" ", "_")[:20]
        base_code = vendor_code
        counter = 1
        while db.query(Vendor).filter(Vendor.vendor_code == vendor_code).first():
            vendor_code = f"{base_code}_{counter}"
            counter += 1
        return vendor_code

    def generate_unique_subdomain(self, db: Session, company_name: str) -> str:
        """Generate a unique subdomain from company name."""
        subdomain = company_name.lower().replace(" ", "-")
        subdomain = "".join(c for c in subdomain if c.isalnum() or c == "-")[:50]
        base_subdomain = subdomain
        counter = 1
        while db.query(Vendor).filter(Vendor.subdomain == subdomain).first():
            subdomain = f"{base_subdomain}-{counter}"
            counter += 1
        return subdomain

    def create_account(
        self,
        db: Session,
        session_id: str,
        email: str,
        password: str,
        first_name: str,
        last_name: str,
        company_name: str,
        phone: str | None = None,
    ) -> AccountCreationResult:
        """
        Create user, company, vendor, and Stripe customer.

        Args:
            db: Database session
            session_id: Signup session ID
            email: User email
            password: User password
            first_name: User first name
            last_name: User last name
            company_name: Company name
            phone: Optional phone number

        Returns:
            AccountCreationResult with IDs

        Raises:
            ResourceNotFoundException: If session not found
            ConflictException: If email already exists
        """
        session = self.get_session_or_raise(session_id)

        # Check if email already exists
        if self.check_email_exists(db, email):
            raise ConflictException(
                message="An account with this email already exists",
            )

        # Generate unique username
        username = self.generate_unique_username(db, email)

        # Create User
        user = User(
            email=email,
            username=username,
            hashed_password=self.auth_manager.hash_password(password),
            first_name=first_name,
            last_name=last_name,
            role="vendor",
            is_active=True,
        )
        db.add(user)
        db.flush()

        # Create Company
        company = Company(
            name=company_name,
            owner_user_id=user.id,
            contact_email=email,
            contact_phone=phone,
        )
        db.add(company)
        db.flush()

        # Generate unique vendor code and subdomain
        vendor_code = self.generate_unique_vendor_code(db, company_name)
        subdomain = self.generate_unique_subdomain(db, company_name)

        # Create Vendor
        vendor = Vendor(
            company_id=company.id,
            vendor_code=vendor_code,
            subdomain=subdomain,
            name=company_name,
            contact_email=email,
            contact_phone=phone,
            is_active=True,
            letzshop_vendor_slug=session.get("letzshop_slug"),
            letzshop_vendor_id=session.get("letzshop_vendor_id"),
        )
        db.add(vendor)
        db.flush()

        # Create VendorUser (owner)
        vendor_user = VendorUser(
            vendor_id=vendor.id,
            user_id=user.id,
            user_type=VendorUserType.OWNER.value,
            is_active=True,
        )
        db.add(vendor_user)

        # Create VendorOnboarding record
        onboarding_service = OnboardingService(db)
        onboarding_service.create_onboarding(vendor.id)

        # Create Stripe Customer
        stripe_customer_id = stripe_service.create_customer(
            vendor=vendor,
            email=email,
            name=f"{first_name} {last_name}",
            metadata={
                "company_name": company_name,
                "tier": session.get("tier_code"),
            },
        )

        # Create VendorSubscription (trial status)
        now = datetime.now(UTC)
        trial_end = now + timedelta(days=settings.stripe_trial_days)

        subscription = VendorSubscription(
            vendor_id=vendor.id,
            tier=session.get("tier_code", TierCode.ESSENTIAL.value),
            status=SubscriptionStatus.TRIAL.value,
            period_start=now,
            period_end=trial_end,
            trial_ends_at=trial_end,
            is_annual=session.get("is_annual", False),
            stripe_customer_id=stripe_customer_id,
        )
        db.add(subscription)

        db.commit()  # noqa: SVC-006 - Atomic account creation needs commit

        # Update session
        self.update_session(session_id, {
            "user_id": user.id,
            "vendor_id": vendor.id,
            "vendor_code": vendor_code,
            "stripe_customer_id": stripe_customer_id,
            "step": "account_created",
        })

        logger.info(
            f"Created account for {email}: user_id={user.id}, vendor_id={vendor.id}"
        )

        return AccountCreationResult(
            user_id=user.id,
            vendor_id=vendor.id,
            vendor_code=vendor_code,
            stripe_customer_id=stripe_customer_id,
        )

    # =========================================================================
    # Payment Setup
    # =========================================================================

    def setup_payment(self, session_id: str) -> tuple[str, str]:
        """
        Create Stripe SetupIntent for card collection.

        Args:
            session_id: Signup session ID

        Returns:
            Tuple of (client_secret, stripe_customer_id)

        Raises:
            EntityNotFoundException: If session not found
            ValidationException: If account not created yet
        """
        session = self.get_session_or_raise(session_id)

        if "stripe_customer_id" not in session:
            raise ValidationException(
                message="Account not created. Please complete step 3 first.",
                field="session_id",
            )

        stripe_customer_id = session["stripe_customer_id"]

        # Create SetupIntent
        setup_intent = stripe_service.create_setup_intent(
            customer_id=stripe_customer_id,
            metadata={
                "session_id": session_id,
                "vendor_id": str(session.get("vendor_id")),
                "tier": session.get("tier_code"),
            },
        )

        # Update session
        self.update_session(session_id, {
            "setup_intent_id": setup_intent.id,
            "step": "payment_pending",
        })

        logger.info(f"Created SetupIntent {setup_intent.id} for session {session_id}")

        return setup_intent.client_secret, stripe_customer_id

    # =========================================================================
    # Welcome Email
    # =========================================================================

    def send_welcome_email(
        self,
        db: Session,
        user: User,
        vendor: Vendor,
        tier_code: str,
        language: str = "fr",
    ) -> None:
        """
        Send welcome email to new vendor.

        Args:
            db: Database session
            user: User who signed up
            vendor: Vendor that was created
            tier_code: Selected tier code
            language: Language for email (default: French)
        """
        try:
            # Get tier name
            tier_enum = TierCode(tier_code)
            tier_name = TIER_LIMITS.get(tier_enum, {}).get("name", tier_code.title())

            # Build login URL
            login_url = f"https://{settings.platform_domain}/vendor/{vendor.vendor_code}/dashboard"

            email_service = EmailService(db)
            email_service.send_template(
                template_code="signup_welcome",
                language=language,
                to_email=user.email,
                to_name=f"{user.first_name} {user.last_name}",
                variables={
                    "first_name": user.first_name,
                    "company_name": vendor.name,
                    "email": user.email,
                    "vendor_code": vendor.vendor_code,
                    "login_url": login_url,
                    "trial_days": settings.stripe_trial_days,
                    "tier_name": tier_name,
                },
                vendor_id=vendor.id,
                user_id=user.id,
                related_type="signup",
            )

            logger.info(f"Welcome email sent to {user.email}")

        except Exception as e:
            # Log error but don't fail signup
            logger.error(f"Failed to send welcome email to {user.email}: {e}")

    # =========================================================================
    # Signup Completion
    # =========================================================================

    def complete_signup(
        self,
        db: Session,
        session_id: str,
        setup_intent_id: str,
    ) -> SignupCompletionResult:
        """
        Complete signup after card collection.

        Args:
            db: Database session
            session_id: Signup session ID
            setup_intent_id: Stripe SetupIntent ID

        Returns:
            SignupCompletionResult

        Raises:
            EntityNotFoundException: If session not found
            ValidationException: If signup incomplete or payment failed
        """
        session = self.get_session_or_raise(session_id)

        vendor_id = session.get("vendor_id")
        stripe_customer_id = session.get("stripe_customer_id")

        if not vendor_id or not stripe_customer_id:
            raise ValidationException(
                message="Incomplete signup. Please start again.",
                field="session_id",
            )

        # Retrieve SetupIntent to get payment method
        setup_intent = stripe_service.get_setup_intent(setup_intent_id)

        if setup_intent.status != "succeeded":
            raise ValidationException(
                message="Card setup not completed. Please try again.",
                field="setup_intent_id",
            )

        payment_method_id = setup_intent.payment_method

        # Attach payment method to customer
        stripe_service.attach_payment_method_to_customer(
            customer_id=stripe_customer_id,
            payment_method_id=payment_method_id,
            set_as_default=True,
        )

        # Update subscription record
        subscription = (
            db.query(VendorSubscription)
            .filter(VendorSubscription.vendor_id == vendor_id)
            .first()
        )

        if subscription:
            subscription.card_collected_at = datetime.now(UTC)
            subscription.stripe_payment_method_id = payment_method_id
            db.commit()  # noqa: SVC-006 - Finalize signup needs commit

        # Get vendor info
        vendor = db.query(Vendor).filter(Vendor.id == vendor_id).first()
        vendor_code = vendor.vendor_code if vendor else session.get("vendor_code")

        trial_ends_at = (
            subscription.trial_ends_at
            if subscription
            else datetime.now(UTC) + timedelta(days=30)
        )

        # Get user for welcome email and token generation
        user_id = session.get("user_id")
        user = db.query(User).filter(User.id == user_id).first() if user_id else None

        # Generate access token for automatic login after signup
        access_token = None
        if user and vendor:
            # Create vendor-scoped JWT token (user is owner since they just signed up)
            token_data = self.auth_manager.create_access_token(
                user=user,
                vendor_id=vendor.id,
                vendor_code=vendor.vendor_code,
                vendor_role="Owner",  # New signup is always the owner
            )
            access_token = token_data["access_token"]
            logger.info(f"Generated access token for new vendor user {user.email}")

        # Send welcome email
        if user and vendor:
            tier_code = session.get("tier_code", TierCode.ESSENTIAL.value)
            self.send_welcome_email(db, user, vendor, tier_code)

        # Clean up session
        self.delete_session(session_id)

        logger.info(f"Completed signup for vendor {vendor_id}")

        # Redirect to onboarding instead of dashboard
        return SignupCompletionResult(
            success=True,
            vendor_code=vendor_code,
            vendor_id=vendor_id,
            redirect_url=f"/vendor/{vendor_code}/onboarding",
            trial_ends_at=trial_ends_at.isoformat(),
            access_token=access_token,
        )


# Singleton instance
platform_signup_service = PlatformSignupService()
