# app/modules/hosting/services/hosted_site_service.py
"""
Hosted site service — CRUD + lifecycle management.

Manages the POC → live website pipeline:
draft → poc_ready → proposal_sent → accepted → live → suspended | cancelled
"""

import logging
import re
from datetime import UTC, datetime

from sqlalchemy import or_
from sqlalchemy.orm import Session, joinedload

from app.modules.hosting.exceptions import (
    DuplicateSlugException,
    HostedSiteNotFoundException,
    InvalidStatusTransitionException,
)
from app.modules.hosting.models import HostedSite, HostedSiteStatus

logger = logging.getLogger(__name__)

# Valid status transitions
ALLOWED_TRANSITIONS: dict[HostedSiteStatus, list[HostedSiteStatus]] = {
    HostedSiteStatus.DRAFT: [HostedSiteStatus.POC_READY, HostedSiteStatus.CANCELLED],
    HostedSiteStatus.POC_READY: [HostedSiteStatus.PROPOSAL_SENT, HostedSiteStatus.CANCELLED],
    HostedSiteStatus.PROPOSAL_SENT: [HostedSiteStatus.ACCEPTED, HostedSiteStatus.CANCELLED],
    HostedSiteStatus.ACCEPTED: [HostedSiteStatus.LIVE, HostedSiteStatus.CANCELLED],
    HostedSiteStatus.LIVE: [HostedSiteStatus.SUSPENDED, HostedSiteStatus.CANCELLED],
    HostedSiteStatus.SUSPENDED: [HostedSiteStatus.LIVE, HostedSiteStatus.CANCELLED],
    HostedSiteStatus.CANCELLED: [],
}


def _slugify(name: str, max_length: int = 30) -> str:
    """Generate a short URL-safe slug from a domain or business name.

    Priority: domain name (clean) > first 3 words of business name > full slug truncated.
    """
    slug = name.lower().strip()
    # If it looks like a domain, extract the hostname part
    for prefix in ["https://", "http://", "www."]:
        if slug.startswith(prefix):
            slug = slug[len(prefix):]
    slug = slug.rstrip("/")
    if "." in slug and " " not in slug:
        # Domain: remove TLD → batirenovation-strasbourg.fr → batirenovation-strasbourg
        slug = slug.rsplit(".", 1)[0]
    else:
        # Business name: take first 3 meaningful words for brevity
        words = re.sub(r"[^a-z0-9\s]", "", slug).split()
        # Skip filler words
        filler = {"the", "le", "la", "les", "de", "du", "des", "et", "and", "und", "die", "der", "das"}
        words = [w for w in words if w not in filler][:3]
        slug = " ".join(words)
    slug = re.sub(r"[^a-z0-9\s-]", "", slug)
    slug = re.sub(r"[\s-]+", "-", slug)
    return slug.strip("-")[:max_length]


class HostedSiteService:
    """Service for hosted site CRUD and lifecycle operations."""

    def get_by_id(self, db: Session, site_id: int) -> HostedSite:
        site = (
            db.query(HostedSite)
            .options(joinedload(HostedSite.client_services))
            .filter(HostedSite.id == site_id)
            .first()
        )
        if not site:
            raise HostedSiteNotFoundException(str(site_id))
        return site

    def get_all(
        self,
        db: Session,
        *,
        page: int = 1,
        per_page: int = 20,
        search: str | None = None,
        status: str | None = None,
    ) -> tuple[list[HostedSite], int]:
        query = db.query(HostedSite).options(joinedload(HostedSite.client_services))

        if search:
            query = query.filter(
                or_(
                    HostedSite.business_name.ilike(f"%{search}%"),
                    HostedSite.contact_email.ilike(f"%{search}%"),
                    HostedSite.live_domain.ilike(f"%{search}%"),
                )
            )
        if status:
            query = query.filter(HostedSite.status == status)

        total = query.count()
        sites = (
            query.order_by(HostedSite.created_at.desc())
            .offset((page - 1) * per_page)
            .limit(per_page)
            .all()
        )
        return sites, total

    def create(self, db: Session, data: dict) -> HostedSite:
        """Create a hosted site with an auto-created Store on the hosting platform.

        Requires either merchant_id or prospect_id in data:
        - merchant_id: store created under this merchant
        - prospect_id: merchant auto-created from prospect data
        """
        from app.modules.tenancy.models import Merchant, Platform, Store
        from app.modules.tenancy.schemas.store import StoreCreate
        from app.modules.tenancy.services.admin_service import admin_service

        business_name = data["business_name"]
        merchant_id = data.get("merchant_id")
        prospect_id = data.get("prospect_id")
        # Prefer domain_name for slug (shorter, cleaner), fall back to business_name
        slug_source = data.get("domain_name") or business_name
        slug = _slugify(slug_source)

        # Find hosting platform
        platform = db.query(Platform).filter(Platform.code == "hosting").first()
        if not platform:
            raise ValueError("Hosting platform not found. Run init_production first.")

        # Resolve merchant
        if merchant_id:
            merchant = db.query(Merchant).filter(Merchant.id == merchant_id).first()
            if not merchant:
                raise ValueError(f"Merchant {merchant_id} not found")
        elif prospect_id:
            merchant = self._create_merchant_from_prospect(db, prospect_id, data)
        else:
            raise ValueError("Either merchant_id or prospect_id is required")

        # Check for duplicate subdomain
        subdomain = slug
        existing = db.query(Store).filter(Store.subdomain == subdomain).first()
        if existing:
            raise DuplicateSlugException(subdomain)

        store_code = slug.upper().replace("-", "_")[:50]
        store_data = StoreCreate(
            merchant_id=merchant.id,
            store_code=store_code,
            subdomain=subdomain,
            name=business_name,
            description=f"POC website for {business_name}",
            platform_ids=[platform.id],
        )
        store = admin_service.create_store(db, store_data)

        site = HostedSite(
            store_id=store.id,
            prospect_id=prospect_id,
            status=HostedSiteStatus.DRAFT,
            business_name=business_name,
            contact_name=data.get("contact_name"),
            contact_email=data.get("contact_email"),
            contact_phone=data.get("contact_phone"),
            internal_notes=data.get("internal_notes"),
        )
        db.add(site)
        db.flush()

        logger.info("Created hosted site: %s (store_id=%d, merchant_id=%d)", site.display_name, store.id, merchant.id)
        return site

    def _create_merchant_from_prospect(self, db: Session, prospect_id: int, data: dict):
        """Create a merchant from prospect data."""
        from app.modules.prospecting.models import Prospect
        from app.modules.tenancy.schemas.merchant import MerchantCreate
        from app.modules.tenancy.services.merchant_service import merchant_service

        prospect = db.query(Prospect).filter(Prospect.id == prospect_id).first()
        if not prospect:
            from app.modules.prospecting.exceptions import ProspectNotFoundException

            raise ProspectNotFoundException(str(prospect_id))

        # Get contact info: prefer form data, fall back to prospect contacts
        contacts = prospect.contacts or []
        email = (
            data.get("contact_email")
            or next((c.value for c in contacts if c.contact_type == "email"), None)
            or f"contact-{prospect_id}@hostwizard.lu"
        )
        phone = data.get("contact_phone") or next(
            (c.value for c in contacts if c.contact_type == "phone"), None
        )
        business_name = data.get("business_name") or prospect.business_name or prospect.domain_name

        merchant_data = MerchantCreate(
            name=business_name,
            contact_email=email,
            contact_phone=phone,
            owner_email=email,
        )
        merchant, _owner_user, _temp_password = merchant_service.create_merchant_with_owner(
            db, merchant_data
        )
        logger.info("Created merchant %s from prospect %d", merchant.name, prospect_id)
        return merchant

    def update(self, db: Session, site_id: int, data: dict) -> HostedSite:
        site = self.get_by_id(db, site_id)

        for field in ["business_name", "contact_name", "contact_email", "contact_phone", "internal_notes"]:
            if field in data and data[field] is not None:
                setattr(site, field, data[field])

        db.flush()
        return site

    def delete(self, db: Session, site_id: int) -> bool:
        """Delete a hosted site and soft-delete the associated store."""
        from app.core.soft_delete import soft_delete

        site = self.get_by_id(db, site_id)
        store = site.store

        db.delete(site)

        # Soft-delete the store created for this site (frees the subdomain)
        if store:
            soft_delete(db, store)
            logger.info("Soft-deleted store %d (subdomain=%s) for site %d", store.id, store.subdomain, site_id)

        db.flush()
        logger.info("Deleted hosted site: %d", site_id)
        return True

    # ── Lifecycle transitions ──────────────────────────────────────────────

    def _transition(self, db: Session, site_id: int, target: HostedSiteStatus) -> HostedSite:
        """Validate and apply a status transition."""
        site = self.get_by_id(db, site_id)
        allowed = ALLOWED_TRANSITIONS.get(site.status, [])
        if target not in allowed:
            raise InvalidStatusTransitionException(site.status.value, target.value)
        site.status = target
        db.flush()
        return site

    def mark_poc_ready(self, db: Session, site_id: int) -> HostedSite:
        site = self._transition(db, site_id, HostedSiteStatus.POC_READY)
        logger.info("Site %d marked POC ready", site_id)
        return site

    def send_proposal(self, db: Session, site_id: int, notes: str | None = None) -> HostedSite:
        site = self._transition(db, site_id, HostedSiteStatus.PROPOSAL_SENT)
        site.proposal_sent_at = datetime.now(UTC)
        if notes:
            site.proposal_notes = notes
        db.flush()
        logger.info("Proposal sent for site %d", site_id)
        return site

    def accept_proposal(
        self, db: Session, site_id: int, merchant_id: int | None = None
    ) -> HostedSite:
        """Accept proposal: create subscription, mark prospect converted.

        The merchant already exists (assigned at site creation time).
        Optionally pass merchant_id to reassign to a different merchant.
        """
        site = self._transition(db, site_id, HostedSiteStatus.ACCEPTED)
        site.proposal_accepted_at = datetime.now(UTC)

        from app.modules.tenancy.models import Merchant, Platform

        # Use provided merchant_id to reassign, or keep existing store merchant
        if merchant_id:
            merchant = db.query(Merchant).filter(Merchant.id == merchant_id).first()
            if not merchant:
                raise ValueError(f"Merchant {merchant_id} not found")
            site.store.merchant_id = merchant.id
            db.flush()
        else:
            merchant = site.store.merchant

        # Create MerchantSubscription on hosting platform
        platform = db.query(Platform).filter(Platform.code == "hosting").first()
        if platform:
            from app.modules.billing.services.subscription_service import (
                subscription_service,
            )

            existing_sub = subscription_service.get_merchant_subscription(
                db, merchant.id, platform.id
            )
            if not existing_sub:
                subscription_service.create_merchant_subscription(
                    db,
                    merchant_id=merchant.id,
                    platform_id=platform.id,
                    tier_code="essential",
                    trial_days=0,
                )
                logger.info("Created subscription for merchant %d on hosting platform", merchant.id)

        # Mark prospect as converted
        if site.prospect_id:
            from app.modules.prospecting.models import Prospect, ProspectStatus

            prospect = db.query(Prospect).filter(Prospect.id == site.prospect_id).first()
            if prospect and prospect.status != ProspectStatus.CONVERTED:
                prospect.status = ProspectStatus.CONVERTED

        db.flush()
        logger.info("Proposal accepted for site %d (merchant=%d)", site_id, merchant.id)
        return site

    def go_live(self, db: Session, site_id: int, domain: str) -> HostedSite:
        """Go live: add domain to store, update site."""
        site = self._transition(db, site_id, HostedSiteStatus.LIVE)
        site.went_live_at = datetime.now(UTC)
        site.live_domain = domain

        # Add domain to store via StoreDomainService
        from app.modules.tenancy.schemas.store_domain import StoreDomainCreate
        from app.modules.tenancy.services.store_domain_service import (
            store_domain_service,
        )

        domain_data = StoreDomainCreate(domain=domain, is_primary=True)
        store_domain_service.add_domain(db, site.store_id, domain_data)

        db.flush()
        logger.info("Site %d went live on %s", site_id, domain)
        return site

    def suspend(self, db: Session, site_id: int) -> HostedSite:
        site = self._transition(db, site_id, HostedSiteStatus.SUSPENDED)
        logger.info("Site %d suspended", site_id)
        return site

    def cancel(self, db: Session, site_id: int) -> HostedSite:
        site = self._transition(db, site_id, HostedSiteStatus.CANCELLED)
        logger.info("Site %d cancelled", site_id)
        return site


hosted_site_service = HostedSiteService()
