# app/modules/cms/services/store_theme_service.py
"""
Store Theme Service

Business logic for store theme management.
Handles theme CRUD operations, preset application, and validation.
"""

from __future__ import annotations

import logging
import re

from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session

from app.modules.cms.exceptions import (
    InvalidColorFormatException,
    InvalidFontFamilyException,
    StoreThemeNotFoundException,
    ThemeOperationException,
    ThemePresetNotFoundException,
    ThemeValidationException,
)
from app.modules.cms.models import StoreTheme
from app.modules.cms.schemas.store_theme import StoreThemeUpdate, ThemePresetPreview
from app.modules.cms.services.theme_presets import (
    THEME_PRESETS,
    apply_preset,
    get_available_presets,
    get_preset_preview,
)
from app.modules.tenancy.exceptions import StoreNotFoundException

logger = logging.getLogger(__name__)


class StoreThemeService:
    """
    Service for managing store themes.

    This service handles:
    - Theme retrieval and creation
    - Theme updates and validation
    - Preset application
    - Default theme generation
    """

    def __init__(self):
        """Initialize the store theme service."""
        self.logger = logging.getLogger(__name__)

    # ============================================================================
    # STORE RETRIEVAL
    # ============================================================================

    def _get_store_by_code(self, db: Session, store_code: str) -> Store:
        """
        Get store by code or raise exception.

        Args:
            db: Database session
            store_code: Store code to lookup

        Returns:
            Store object

        Raises:
            StoreNotFoundException: If store not found
        """
        from app.modules.tenancy.services.store_service import store_service

        store = store_service.get_store_by_code_or_subdomain(db, store_code)

        if not store:
            self.logger.warning(f"Store not found: {store_code}")
            raise StoreNotFoundException(store_code, identifier_type="code")

        return store

    # ============================================================================
    # THEME RETRIEVAL
    # ============================================================================

    def get_theme(self, db: Session, store_code: str) -> dict:
        """
        Get theme for store. Returns default if no custom theme exists.

        Args:
            db: Database session
            store_code: Store code

        Returns:
            Theme dictionary

        Raises:
            StoreNotFoundException: If store not found
        """
        self.logger.info(f"Getting theme for store: {store_code}")

        # Verify store exists
        store = self._get_store_by_code(db, store_code)

        # Get theme
        theme = db.query(StoreTheme).filter(StoreTheme.store_id == store.id).first()

        if not theme:
            self.logger.info(
                f"No custom theme for store {store_code}, returning default"
            )
            return self._get_default_theme()

        return theme.to_dict()

    def _get_default_theme(self) -> dict:
        """
        Get default theme configuration.

        Returns:
            Default theme dictionary
        """
        return {
            "theme_name": "default",
            "colors": {
                "primary": "#6366f1",
                "secondary": "#8b5cf6",
                "accent": "#ec4899",
                "background": "#ffffff",
                "text": "#1f2937",
                "border": "#e5e7eb",
            },
            "fonts": {"heading": "Inter, sans-serif", "body": "Inter, sans-serif"},
            "branding": {
                "logo": None,
                "logo_dark": None,
                "favicon": None,
                "banner": None,
            },
            "layout": {"style": "grid", "header": "fixed", "product_card": "modern"},
            "social_links": {},
            "custom_css": None,
            "css_variables": {
                "--color-primary": "#6366f1",
                "--color-secondary": "#8b5cf6",
                "--color-accent": "#ec4899",
                "--color-background": "#ffffff",
                "--color-text": "#1f2937",
                "--color-border": "#e5e7eb",
                "--font-heading": "Inter, sans-serif",
                "--font-body": "Inter, sans-serif",
            },
        }

    # ============================================================================
    # THEME UPDATE
    # ============================================================================

    def update_theme(
        self, db: Session, store_code: str, theme_data: StoreThemeUpdate
    ) -> StoreTheme:
        """
        Update or create theme for store.

        Args:
            db: Database session
            store_code: Store code
            theme_data: Theme update data

        Returns:
            Updated StoreTheme object

        Raises:
            StoreNotFoundException: If store not found
            ThemeValidationException: If theme data invalid
            ThemeOperationException: If update fails
        """
        self.logger.info(f"Updating theme for store: {store_code}")

        try:
            # Verify store exists
            store = self._get_store_by_code(db, store_code)

            # Get or create theme
            theme = (
                db.query(StoreTheme).filter(StoreTheme.store_id == store.id).first()
            )

            if not theme:
                self.logger.info(f"Creating new theme for store {store_code}")
                theme = StoreTheme(store_id=store.id, is_active=True)
                db.add(theme)

            # Validate theme data before applying
            self._validate_theme_data(theme_data)

            # Update theme fields
            self._apply_theme_updates(theme, theme_data)

            # Flush changes
            db.flush()
            db.refresh(theme)

            self.logger.info(f"Theme updated successfully for store {store_code}")
            return theme

        except (StoreNotFoundException, ThemeValidationException):
            # Re-raise custom exceptions
            raise

        except SQLAlchemyError as e:
            self.logger.error(f"Failed to update theme for store {store_code}: {e}")
            raise ThemeOperationException(
                operation="update", store_code=store_code, reason=str(e)
            )

    def _apply_theme_updates(
        self, theme: StoreTheme, theme_data: StoreThemeUpdate
    ) -> None:
        """
        Apply theme updates to theme object.

        Args:
            theme: StoreTheme object to update
            theme_data: Theme update data
        """
        # Update theme name
        if theme_data.theme_name:
            theme.theme_name = theme_data.theme_name

        # Update colors
        if theme_data.colors:
            theme.colors = theme_data.colors

        # Update fonts
        if theme_data.fonts:
            if theme_data.fonts.get("heading"):
                theme.font_family_heading = theme_data.fonts["heading"]
            if theme_data.fonts.get("body"):
                theme.font_family_body = theme_data.fonts["body"]

        # Update branding
        if theme_data.branding:
            if theme_data.branding.get("logo") is not None:
                theme.logo_url = theme_data.branding["logo"]
            if theme_data.branding.get("logo_dark") is not None:
                theme.logo_dark_url = theme_data.branding["logo_dark"]
            if theme_data.branding.get("favicon") is not None:
                theme.favicon_url = theme_data.branding["favicon"]
            if theme_data.branding.get("banner") is not None:
                theme.banner_url = theme_data.branding["banner"]

        # Update layout
        if theme_data.layout:
            if theme_data.layout.get("style"):
                theme.layout_style = theme_data.layout["style"]
            if theme_data.layout.get("header"):
                theme.header_style = theme_data.layout["header"]
            if theme_data.layout.get("product_card"):
                theme.product_card_style = theme_data.layout["product_card"]

        # Update custom CSS
        if theme_data.custom_css is not None:
            theme.custom_css = theme_data.custom_css

        # Update social links
        if theme_data.social_links:
            theme.social_links = theme_data.social_links

    # ============================================================================
    # PRESET OPERATIONS
    # ============================================================================

    def apply_theme_preset(
        self, db: Session, store_code: str, preset_name: str
    ) -> StoreTheme:
        """
        Apply a theme preset to store.

        Args:
            db: Database session
            store_code: Store code
            preset_name: Name of preset to apply

        Returns:
            Updated StoreTheme object

        Raises:
            StoreNotFoundException: If store not found
            ThemePresetNotFoundException: If preset not found
            ThemeOperationException: If application fails
        """
        self.logger.info(f"Applying preset '{preset_name}' to store {store_code}")

        try:
            # Validate preset name
            if preset_name not in THEME_PRESETS:
                available = get_available_presets()
                raise ThemePresetNotFoundException(preset_name, available)

            # Verify store exists
            store = self._get_store_by_code(db, store_code)

            # Get or create theme
            theme = (
                db.query(StoreTheme).filter(StoreTheme.store_id == store.id).first()
            )

            if not theme:
                self.logger.info(f"Creating new theme for store {store_code}")
                theme = StoreTheme(store_id=store.id)
                db.add(theme)

            # Apply preset using helper function
            apply_preset(theme, preset_name)

            # Flush changes
            db.flush()
            db.refresh(theme)

            self.logger.info(
                f"Preset '{preset_name}' applied successfully to store {store_code}"
            )
            return theme

        except (StoreNotFoundException, ThemePresetNotFoundException):
            # Re-raise custom exceptions
            raise

        except SQLAlchemyError as e:
            self.logger.error(f"Failed to apply preset to store {store_code}: {e}")
            raise ThemeOperationException(
                operation="apply_preset", store_code=store_code, reason=str(e)
            )

    def get_available_presets(self) -> list[ThemePresetPreview]:
        """
        Get list of available theme presets.

        Returns:
            List of preset preview objects
        """
        self.logger.debug("Getting available presets")

        preset_names = get_available_presets()
        presets = []

        for name in preset_names:
            preview = get_preset_preview(name)
            presets.append(preview)

        return presets

    # ============================================================================
    # THEME DELETION
    # ============================================================================

    def delete_theme(self, db: Session, store_code: str) -> dict:
        """
        Delete custom theme for store (reverts to default).

        Args:
            db: Database session
            store_code: Store code

        Returns:
            Success message dictionary

        Raises:
            StoreNotFoundException: If store not found
            StoreThemeNotFoundException: If no custom theme exists
            ThemeOperationException: If deletion fails
        """
        self.logger.info(f"Deleting theme for store: {store_code}")

        try:
            # Verify store exists
            store = self._get_store_by_code(db, store_code)

            # Get theme
            theme = (
                db.query(StoreTheme).filter(StoreTheme.store_id == store.id).first()
            )

            if not theme:
                raise StoreThemeNotFoundException(store_code)

            # Delete theme
            db.delete(theme)

            self.logger.info(f"Theme deleted for store {store_code}")
            return {
                "message": "Theme deleted successfully. Store will use default theme."
            }

        except (StoreNotFoundException, StoreThemeNotFoundException):
            # Re-raise custom exceptions
            raise

        except SQLAlchemyError as e:
            self.logger.error(f"Failed to delete theme for store {store_code}: {e}")
            raise ThemeOperationException(
                operation="delete", store_code=store_code, reason=str(e)
            )

    # ============================================================================
    # VALIDATION
    # ============================================================================

    def _validate_theme_data(self, theme_data: StoreThemeUpdate) -> None:
        """
        Validate theme data before applying.

        Args:
            theme_data: Theme update data

        Raises:
            ThemeValidationException: If validation fails
            InvalidColorFormatException: If color format invalid
            InvalidFontFamilyException: If font family invalid
        """
        # Validate colors
        if theme_data.colors:
            for color_key, color_value in theme_data.colors.items():
                if not self._is_valid_color(color_value):
                    raise InvalidColorFormatException(color_value, color_key)

        # Validate fonts
        if theme_data.fonts:
            for font_key, font_value in theme_data.fonts.items():
                if not self._is_valid_font(font_value):
                    raise InvalidFontFamilyException(font_value, font_key)

        # Validate layout values
        if theme_data.layout:
            valid_layouts = {
                "style": ["grid", "list", "masonry"],
                "header": ["fixed", "static", "transparent"],
                "product_card": ["modern", "classic", "minimal"],
            }

            for layout_key, layout_value in theme_data.layout.items():
                if layout_key in valid_layouts:
                    if layout_value not in valid_layouts[layout_key]:
                        raise ThemeValidationException(
                            message=f"Invalid {layout_key} value: {layout_value}",
                            field=layout_key,
                            validation_errors={
                                layout_key: f"Must be one of: {', '.join(valid_layouts[layout_key])}"
                            },
                        )

    def _is_valid_color(self, color: str) -> bool:
        """
        Validate color format (hex color).

        Args:
            color: Color string to validate

        Returns:
            True if valid, False otherwise
        """
        if not color:
            return False

        # Check for hex color format (#RGB or #RRGGBB)
        hex_pattern = r"^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$"
        return bool(re.match(hex_pattern, color))

    def _is_valid_font(self, font: str) -> bool:
        """
        Validate font family format.

        Args:
            font: Font family string to validate

        Returns:
            True if valid, False otherwise
        """
        if not font or len(font) < 3:
            return False

        # Basic validation - font should not be empty and should be reasonable length
        return len(font) <= 200


# ============================================================================
# SERVICE INSTANCE
# ============================================================================

store_theme_service = StoreThemeService()
