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

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

import logging
from collections.abc import Callable
from datetime import UTC, datetime
from typing import Any

from sqlalchemy import func
from sqlalchemy.orm import Session

from app.modules.marketplace.exceptions import SyncError
from app.modules.marketplace.models import LetzshopStoreCache

from .client_service import LetzshopClient

logger = logging.getLogger(__name__)


class LetzshopStoreSyncService:
    """
    Service for syncing Letzshop store directory.

    Usage:
        service = LetzshopStoreSyncService(db)
        stats = service.sync_all_stores()
    """

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

    def sync_all_stores(
        self,
        progress_callback: Callable[[int, int, int], None] | None = None,
        max_pages: int | None = None,
    ) -> dict[str, Any]:
        """
        Sync all stores 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 store directory sync...")

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

        try:
            # Fetch all stores
            stores = client.get_all_stores_paginated(
                page_size=50,
                max_pages=max_pages,
                progress_callback=progress_callback,
            )

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

            # Process each store
            for store_data in stores:
                try:
                    result = self._upsert_store(store_data)
                    if result == "created":
                        stats["created"] += 1
                    elif result == "updated":
                        stats["updated"] += 1
                except Exception as e:
                    stats["errors"] += 1
                    error_info = {
                        "store_id": store_data.get("id"),
                        "slug": store_data.get("slug"),
                        "error": str(e),
                    }
                    stats["error_details"].append(error_info)
                    logger.error(f"Error processing store {store_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"Store 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_store(self, store_data: dict[str, Any]) -> str:
        """
        Insert or update a store in the cache.

        Args:
            store_data: Raw store data from Letzshop API.

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

        if not letzshop_id or not slug:
            raise SyncError("Store missing required id or slug")

        # Parse the store data
        parsed = self._parse_store_data(store_data)

        # Check if exists
        existing = (
            self.db.query(LetzshopStoreCache)
            .filter(LetzshopStoreCache.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_store_id", "claimed_at"):
                    setattr(existing, key, value)
            existing.last_synced_at = datetime.now(UTC)
            return "updated"
        # Create new record
        cache_entry = LetzshopStoreCache(
            **parsed,
            last_synced_at=datetime.now(UTC),
        )
        self.db.add(cache_entry)
        return "created"

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

        Args:
            data: Raw store data from Letzshop API.

        Returns:
            Dictionary of parsed fields for LetzshopStoreCache.
        """
        # 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("storeCategories") 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"),
            "merchant_name": data.get("merchantName") 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_store(self, slug: str) -> LetzshopStoreCache | None:
        """
        Sync a single store by slug.

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

        Args:
            slug: The store's URL slug.

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

        try:
            store_data = client.get_store_by_slug(slug)

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

            result = self._upsert_store(store_data)
            self.db.commit()

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

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

        finally:
            client.close()

    def get_cached_store(self, slug: str) -> LetzshopStoreCache | None:
        """
        Get a store from cache by slug.

        Args:
            slug: The store's URL slug.

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

    def search_cached_stores(
        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[LetzshopStoreCache], int]:
        """
        Search cached stores with filters.

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

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

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

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

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

        if only_unclaimed:
            query = query.filter(
                LetzshopStoreCache.claimed_by_store_id.is_(None)
            )

        # Get total count
        total = query.count()

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

        return stores, total

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

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

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

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

        return {
            "total_stores": total,
            "active_stores": active,
            "claimed_stores": claimed,
            "unclaimed_stores": active - claimed,
            "unique_cities": cities,
            "last_synced_at": last_synced.isoformat() if last_synced else None,
        }

    def mark_store_claimed(
        self,
        letzshop_slug: str,
        store_id: int,
    ) -> bool:
        """
        Mark a Letzshop store as claimed by a platform store.

        Args:
            letzshop_slug: The Letzshop store slug.
            store_id: The platform store ID that claimed it.

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

        if not cache_entry:
            return False

        cache_entry.claimed_by_store_id = store_id
        cache_entry.claimed_at = datetime.now(UTC)
        self.db.commit()

        logger.info(f"Store {letzshop_slug} claimed by store_id={store_id}")
        return True

    def create_store_from_cache(
        self,
        letzshop_slug: str,
        merchant_id: int,
    ) -> dict[str, Any]:
        """
        Create a platform store from a cached Letzshop store.

        Args:
            letzshop_slug: The Letzshop store slug.
            merchant_id: The merchant ID to create the store under.

        Returns:
            Dictionary with created store info.

        Raises:
            SyncError: If store not found, already claimed, or merchant not found.
        """
        import random

        from app.modules.tenancy.schemas.store import StoreCreate
        from app.modules.tenancy.services.admin_service import admin_service
        from app.modules.tenancy.services.merchant_service import merchant_service
        from app.modules.tenancy.services.store_service import store_service

        # Get cache entry
        cache_entry = self.get_cached_store(letzshop_slug)
        if not cache_entry:
            raise SyncError(f"Letzshop store '{letzshop_slug}' not found in cache")

        if cache_entry.is_claimed:
            raise SyncError(
                f"Letzshop store '{cache_entry.name}' is already claimed "
                f"by store ID {cache_entry.claimed_by_store_id}"
            )

        # Verify merchant exists
        merchant = merchant_service.get_merchant_by_id(self.db, merchant_id)
        if not merchant:
            raise SyncError(f"Merchant with ID {merchant_id} not found")

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

        # Check if store code already exists
        if store_service.is_store_code_taken(self.db, store_code):
            store_code = f"{store_code[:16]}_{random.randint(100, 999)}"  # noqa: SEC042

        # Generate subdomain from slug
        subdomain = letzshop_slug.lower().replace("_", "-")[:30]
        if store_service.is_subdomain_taken(self.db, subdomain):
            subdomain = f"{subdomain[:26]}-{random.randint(100, 999)}"  # noqa: SEC042

        # Create store data from cache
        address = f"{cache_entry.street or ''} {cache_entry.street_number or ''}".strip()
        store_data = StoreCreate(
            name=cache_entry.name,
            store_code=store_code,
            subdomain=subdomain,
            merchant_id=merchant_id,
            email=cache_entry.email or merchant.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 store
        store = admin_service.create_store(self.db, store_data)

        # Mark the Letzshop store as claimed (commits internally)  # SVC-006
        self.mark_store_claimed(letzshop_slug, store.id)

        logger.info(
            f"Created store {store.store_code} from Letzshop store {letzshop_slug}"
        )

        return {
            "id": store.id,
            "store_code": store.store_code,
            "name": store.name,
            "subdomain": store.subdomain,
            "merchant_id": store.merchant_id,
        }


# Singleton-style function for easy access
def get_store_sync_service(db: Session) -> LetzshopStoreSyncService:
    """Get a store sync service instance."""
    return LetzshopStoreSyncService(db)
