# app/services/letzshop/vendor_sync_service.py
"""
Service for syncing Letzshop vendor directory to local cache.

Fetches vendor data from Letzshop's public GraphQL API and stores it
in the letzshop_vendor_cache table for fast lookups during signup.
"""

import logging
from datetime import UTC, datetime
from typing import Any, Callable

from sqlalchemy import func
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.orm import Session

from .client_service import LetzshopClient
from app.modules.marketplace.models import LetzshopVendorCache

logger = logging.getLogger(__name__)


class LetzshopVendorSyncService:
    """
    Service for syncing Letzshop vendor directory.

    Usage:
        service = LetzshopVendorSyncService(db)
        stats = service.sync_all_vendors()
    """

    def __init__(self, db: Session):
        """Initialize the sync service."""
        self.db = db

    def sync_all_vendors(
        self,
        progress_callback: Callable[[int, int, int], None] | None = None,
        max_pages: int | None = None,
    ) -> dict[str, Any]:
        """
        Sync all vendors from Letzshop to local cache.

        Args:
            progress_callback: Optional callback(page, fetched, total) for progress.

        Returns:
            Dictionary with sync statistics.
        """
        stats = {
            "started_at": datetime.now(UTC),
            "total_fetched": 0,
            "created": 0,
            "updated": 0,
            "errors": 0,
            "error_details": [],
        }

        logger.info("Starting Letzshop vendor directory sync...")

        # Create client (no API key needed for public vendor data)
        client = LetzshopClient(api_key="")

        try:
            # Fetch all vendors
            vendors = client.get_all_vendors_paginated(
                page_size=50,
                max_pages=max_pages,
                progress_callback=progress_callback,
            )

            stats["total_fetched"] = len(vendors)
            logger.info(f"Fetched {len(vendors)} vendors from Letzshop")

            # Process each vendor
            for vendor_data in vendors:
                try:
                    result = self._upsert_vendor(vendor_data)
                    if result == "created":
                        stats["created"] += 1
                    elif result == "updated":
                        stats["updated"] += 1
                except Exception as e:
                    stats["errors"] += 1
                    error_info = {
                        "vendor_id": vendor_data.get("id"),
                        "slug": vendor_data.get("slug"),
                        "error": str(e),
                    }
                    stats["error_details"].append(error_info)
                    logger.error(f"Error processing vendor {vendor_data.get('slug')}: {e}")

            # Commit all changes
            self.db.commit()
            logger.info(
                f"Sync complete: {stats['created']} created, "
                f"{stats['updated']} updated, {stats['errors']} errors"
            )

        except Exception as e:
            self.db.rollback()
            logger.error(f"Vendor sync failed: {e}")
            stats["error"] = str(e)
            raise

        finally:
            client.close()

        stats["completed_at"] = datetime.now(UTC)
        stats["duration_seconds"] = (
            stats["completed_at"] - stats["started_at"]
        ).total_seconds()

        return stats

    def _upsert_vendor(self, vendor_data: dict[str, Any]) -> str:
        """
        Insert or update a vendor in the cache.

        Args:
            vendor_data: Raw vendor data from Letzshop API.

        Returns:
            "created" or "updated" indicating the operation performed.
        """
        letzshop_id = vendor_data.get("id")
        slug = vendor_data.get("slug")

        if not letzshop_id or not slug:
            raise ValueError("Vendor missing required id or slug")

        # Parse the vendor data
        parsed = self._parse_vendor_data(vendor_data)

        # Check if exists
        existing = (
            self.db.query(LetzshopVendorCache)
            .filter(LetzshopVendorCache.letzshop_id == letzshop_id)
            .first()
        )

        if existing:
            # Update existing record (preserve claimed status)
            for key, value in parsed.items():
                if key not in ("claimed_by_vendor_id", "claimed_at"):
                    setattr(existing, key, value)
            existing.last_synced_at = datetime.now(UTC)
            return "updated"
        else:
            # Create new record
            cache_entry = LetzshopVendorCache(
                **parsed,
                last_synced_at=datetime.now(UTC),
            )
            self.db.add(cache_entry)
            return "created"

    def _parse_vendor_data(self, data: dict[str, Any]) -> dict[str, Any]:
        """
        Parse raw Letzshop vendor data into cache model fields.

        Args:
            data: Raw vendor data from Letzshop API.

        Returns:
            Dictionary of parsed fields for LetzshopVendorCache.
        """
        # Extract location
        location = data.get("location") or {}
        country = location.get("country") or {}

        # Extract descriptions
        description = data.get("description") or {}

        # Extract opening hours
        opening_hours = data.get("openingHours") or {}

        # Extract categories (list of translated name objects)
        categories = []
        for cat in data.get("vendorCategories") or []:
            cat_name = cat.get("name") or {}
            # Prefer English, fallback to French or German
            name = cat_name.get("en") or cat_name.get("fr") or cat_name.get("de")
            if name:
                categories.append(name)

        # Extract social media URLs
        social_links = []
        for link in data.get("socialMediaLinks") or []:
            url = link.get("url")
            if url:
                social_links.append(url)

        # Extract background image
        bg_image = data.get("backgroundImage") or {}

        return {
            "letzshop_id": data.get("id"),
            "slug": data.get("slug"),
            "name": data.get("name"),
            "company_name": data.get("companyName") or data.get("legalName"),
            "is_active": data.get("active", True),
            # Descriptions
            "description_en": description.get("en"),
            "description_fr": description.get("fr"),
            "description_de": description.get("de"),
            # Contact
            "email": data.get("email"),
            "phone": data.get("phone"),
            "fax": data.get("fax"),
            "website": data.get("homepage"),
            # Location
            "street": location.get("street"),
            "street_number": location.get("number"),
            "city": location.get("city"),
            "zipcode": location.get("zipcode"),
            "country_iso": country.get("iso", "LU"),
            "latitude": str(data.get("lat")) if data.get("lat") else None,
            "longitude": str(data.get("lng")) if data.get("lng") else None,
            # Categories and media
            "categories": categories,
            "background_image_url": bg_image.get("url"),
            "social_media_links": social_links,
            # Opening hours
            "opening_hours_en": opening_hours.get("en"),
            "opening_hours_fr": opening_hours.get("fr"),
            "opening_hours_de": opening_hours.get("de"),
            # Representative
            "representative_name": data.get("representative"),
            "representative_title": data.get("representativeTitle"),
            # Raw data for reference
            "raw_data": data,
        }

    def sync_single_vendor(self, slug: str) -> LetzshopVendorCache | None:
        """
        Sync a single vendor by slug.

        Useful for on-demand refresh when a user looks up a vendor.

        Args:
            slug: The vendor's URL slug.

        Returns:
            The updated/created cache entry, or None if not found.
        """
        client = LetzshopClient(api_key="")

        try:
            vendor_data = client.get_vendor_by_slug(slug)

            if not vendor_data:
                logger.warning(f"Vendor not found on Letzshop: {slug}")
                return None

            result = self._upsert_vendor(vendor_data)
            self.db.commit()

            logger.info(f"Single vendor sync: {slug} ({result})")

            return (
                self.db.query(LetzshopVendorCache)
                .filter(LetzshopVendorCache.slug == slug)
                .first()
            )

        finally:
            client.close()

    def get_cached_vendor(self, slug: str) -> LetzshopVendorCache | None:
        """
        Get a vendor from cache by slug.

        Args:
            slug: The vendor's URL slug.

        Returns:
            Cache entry or None if not found.
        """
        return (
            self.db.query(LetzshopVendorCache)
            .filter(LetzshopVendorCache.slug == slug.lower())
            .first()
        )

    def search_cached_vendors(
        self,
        search: str | None = None,
        city: str | None = None,
        category: str | None = None,
        only_unclaimed: bool = False,
        page: int = 1,
        limit: int = 20,
    ) -> tuple[list[LetzshopVendorCache], int]:
        """
        Search cached vendors with filters.

        Args:
            search: Search term for name.
            city: Filter by city.
            category: Filter by category.
            only_unclaimed: Only return vendors not yet claimed.
            page: Page number (1-indexed).
            limit: Items per page.

        Returns:
            Tuple of (vendors list, total count).
        """
        query = self.db.query(LetzshopVendorCache).filter(
            LetzshopVendorCache.is_active == True  # noqa: E712
        )

        if search:
            search_term = f"%{search.lower()}%"
            query = query.filter(
                func.lower(LetzshopVendorCache.name).like(search_term)
            )

        if city:
            query = query.filter(
                func.lower(LetzshopVendorCache.city) == city.lower()
            )

        if category:
            # Search in JSON array
            query = query.filter(
                LetzshopVendorCache.categories.contains([category])
            )

        if only_unclaimed:
            query = query.filter(
                LetzshopVendorCache.claimed_by_vendor_id.is_(None)
            )

        # Get total count
        total = query.count()

        # Apply pagination
        offset = (page - 1) * limit
        vendors = (
            query.order_by(LetzshopVendorCache.name)
            .offset(offset)
            .limit(limit)
            .all()
        )

        return vendors, total

    def get_sync_stats(self) -> dict[str, Any]:
        """
        Get statistics about the vendor cache.

        Returns:
            Dictionary with cache statistics.
        """
        total = self.db.query(LetzshopVendorCache).count()
        active = (
            self.db.query(LetzshopVendorCache)
            .filter(LetzshopVendorCache.is_active == True)  # noqa: E712
            .count()
        )
        claimed = (
            self.db.query(LetzshopVendorCache)
            .filter(LetzshopVendorCache.claimed_by_vendor_id.isnot(None))
            .count()
        )

        # Get last sync time
        last_synced = (
            self.db.query(func.max(LetzshopVendorCache.last_synced_at)).scalar()
        )

        # Get unique cities
        cities = (
            self.db.query(LetzshopVendorCache.city)
            .filter(LetzshopVendorCache.city.isnot(None))
            .distinct()
            .count()
        )

        return {
            "total_vendors": total,
            "active_vendors": active,
            "claimed_vendors": claimed,
            "unclaimed_vendors": active - claimed,
            "unique_cities": cities,
            "last_synced_at": last_synced.isoformat() if last_synced else None,
        }

    def mark_vendor_claimed(
        self,
        letzshop_slug: str,
        vendor_id: int,
    ) -> bool:
        """
        Mark a Letzshop vendor as claimed by a platform vendor.

        Args:
            letzshop_slug: The Letzshop vendor slug.
            vendor_id: The platform vendor ID that claimed it.

        Returns:
            True if successful, False if vendor not found.
        """
        cache_entry = self.get_cached_vendor(letzshop_slug)

        if not cache_entry:
            return False

        cache_entry.claimed_by_vendor_id = vendor_id
        cache_entry.claimed_at = datetime.now(UTC)
        self.db.commit()

        logger.info(f"Vendor {letzshop_slug} claimed by vendor_id={vendor_id}")
        return True

    def create_vendor_from_cache(
        self,
        letzshop_slug: str,
        company_id: int,
    ) -> dict[str, Any]:
        """
        Create a platform vendor from a cached Letzshop vendor.

        Args:
            letzshop_slug: The Letzshop vendor slug.
            company_id: The company ID to create the vendor under.

        Returns:
            Dictionary with created vendor info.

        Raises:
            ValueError: If vendor not found, already claimed, or company not found.
        """
        import random

        from sqlalchemy import func

        from app.modules.tenancy.services.admin_service import admin_service
        from app.modules.tenancy.models import Company
        from app.modules.tenancy.models import Vendor
        from app.modules.tenancy.schemas.vendor import VendorCreate

        # Get cache entry
        cache_entry = self.get_cached_vendor(letzshop_slug)
        if not cache_entry:
            raise ValueError(f"Letzshop vendor '{letzshop_slug}' not found in cache")

        if cache_entry.is_claimed:
            raise ValueError(
                f"Letzshop vendor '{cache_entry.name}' is already claimed "
                f"by vendor ID {cache_entry.claimed_by_vendor_id}"
            )

        # Verify company exists
        company = self.db.query(Company).filter(Company.id == company_id).first()
        if not company:
            raise ValueError(f"Company with ID {company_id} not found")

        # Generate vendor code from slug
        vendor_code = letzshop_slug.upper().replace("-", "_")[:20]

        # Check if vendor code already exists
        existing = (
            self.db.query(Vendor)
            .filter(func.upper(Vendor.vendor_code) == vendor_code)
            .first()
        )
        if existing:
            vendor_code = f"{vendor_code[:16]}_{random.randint(100, 999)}"

        # Generate subdomain from slug
        subdomain = letzshop_slug.lower().replace("_", "-")[:30]
        existing_subdomain = (
            self.db.query(Vendor)
            .filter(func.lower(Vendor.subdomain) == subdomain)
            .first()
        )
        if existing_subdomain:
            subdomain = f"{subdomain[:26]}-{random.randint(100, 999)}"

        # Create vendor data from cache
        address = f"{cache_entry.street or ''} {cache_entry.street_number or ''}".strip()
        vendor_data = VendorCreate(
            name=cache_entry.name,
            vendor_code=vendor_code,
            subdomain=subdomain,
            company_id=company_id,
            email=cache_entry.email or company.email,
            phone=cache_entry.phone,
            description=cache_entry.description_en or cache_entry.description_fr or "",
            city=cache_entry.city,
            country=cache_entry.country_iso or "LU",
            website=cache_entry.website,
            address_line_1=address or None,
            postal_code=cache_entry.zipcode,
        )

        # Create vendor
        vendor = admin_service.create_vendor(self.db, vendor_data)

        # Mark the Letzshop vendor as claimed (commits internally)  # noqa: SVC-006
        self.mark_vendor_claimed(letzshop_slug, vendor.id)

        logger.info(
            f"Created vendor {vendor.vendor_code} from Letzshop vendor {letzshop_slug}"
        )

        return {
            "id": vendor.id,
            "vendor_code": vendor.vendor_code,
            "name": vendor.name,
            "subdomain": vendor.subdomain,
            "company_id": vendor.company_id,
        }


# Singleton-style function for easy access
def get_vendor_sync_service(db: Session) -> LetzshopVendorSyncService:
    """Get a vendor sync service instance."""
    return LetzshopVendorSyncService(db)
