"""
Module Settings — Application Service

سرویس لایه Application برای مدیریت تنظیمات ماژول‌ها.
هماهنگ‌کننده بین API، Domain و Infrastructure.
"""
from typing import Any, Optional
from uuid import UUID

from django.core.cache import cache
from django.db import transaction

from .domain import ModuleSettings, SettingsPath
from .exceptions import (
    InvalidSettingsPath,
    SettingsNotFoundException,
    SettingsValidationError,
)
from .models import ModuleSettingsModel
from .schema_registry import settings_schema_registry


CACHE_KEY_PREFIX = "module_settings"
CACHE_TTL = 60 * 15  # 15 minutes


def _cache_key(tenant_id: UUID, module_key: str) -> str:
    return f"{CACHE_KEY_PREFIX}:{tenant_id}:{module_key}"


class ModuleSettingsService:
    """
    سرویس مدیریت تنظیمات ماژول‌ها.

    مسئولیت‌ها:
    - دریافت تنظیمات ماژول (با cache)
    - دریافت مقدار یک تنظیم خاص
    - تغییر مقدار تنظیمات (با validation)
    - ایجاد تنظیمات پیش‌فرض
    """

    # ─── Read ─────────────────────────────────────────────

    @staticmethod
    def get(module_key: str, tenant_id: UUID) -> dict[str, Any]:
        """
        دریافت تمام تنظیمات یک ماژول برای یک tenant.

        اگر تنظیمات وجود نداشت، مقادیر پیش‌فرض برگردانده می‌شود.
        نتیجه cache می‌شود.

        Args:
            module_key: کلید ماژول
            tenant_id: شناسه tenant

        Returns:
            دیکشنری تنظیمات
        """
        cache_k = _cache_key(tenant_id, module_key)
        try:
            cached = cache.get(cache_k)
        except Exception:
            cached = None
        if cached is not None:
            return cached

        try:
            obj = ModuleSettingsModel.all_tenants.get(
                tenant_id=tenant_id,
                module_key=module_key,
            )
            data = obj.settings_data or {}
        except ModuleSettingsModel.DoesNotExist:
            # Return defaults if no settings saved yet
            if settings_schema_registry.has_schema(module_key):
                data = settings_schema_registry.get_defaults(module_key)
            else:
                data = {}

        # Merge with defaults to fill any new fields
        if settings_schema_registry.has_schema(module_key):
            defaults = settings_schema_registry.get_defaults(module_key)
            data = ModuleSettingsService._deep_merge_defaults(data, defaults)

        try:
            cache.set(cache_k, data, CACHE_TTL)
        except Exception:
            pass
        return data

    @staticmethod
    def get_value(
        module_key: str,
        tenant_id: UUID,
        path: str,
        default: Any = None,
    ) -> Any:
        """
        دریافت مقدار یک تنظیم خاص.

        Args:
            module_key: کلید ماژول
            tenant_id: شناسه tenant
            path: مسیر تنظیم (مثلاً "leave.max_days_per_request")
            default: مقدار پیش‌فرض اگر تنظیم یافت نشد

        Returns:
            مقدار تنظیم
        """
        data = ModuleSettingsService.get(module_key, tenant_id)
        try:
            settings_path = SettingsPath(path=path)
        except ValueError:
            raise InvalidSettingsPath(path=path, module_key=module_key)

        current = data
        for segment in settings_path.segments:
            if isinstance(current, dict) and segment in current:
                current = current[segment]
            else:
                return default
        return current

    @staticmethod
    def get_section(
        module_key: str,
        tenant_id: UUID,
        section_key: str,
    ) -> dict[str, Any]:
        """
        دریافت تنظیمات یک بخش.

        Args:
            module_key: کلید ماژول
            tenant_id: شناسه tenant
            section_key: کلید بخش

        Returns:
            دیکشنری تنظیمات بخش
        """
        data = ModuleSettingsService.get(module_key, tenant_id)
        return data.get(section_key, {})

    # ─── Write ────────────────────────────────────────────

    @staticmethod
    @transaction.atomic
    def save_settings(
        module_key: str,
        tenant_id: UUID,
        settings_data: dict[str, Any],
        user_id: Optional[UUID] = None,
    ) -> dict[str, Any]:
        """
        ذخیره تنظیمات کامل یک ماژول.

        Args:
            module_key: کلید ماژول
            tenant_id: شناسه tenant
            settings_data: دیکشنری تنظیمات
            user_id: شناسه کاربر

        Returns:
            دیکشنری تنظیمات ذخیره‌شده

        Raises:
            SettingsValidationError: در صورت نامعتبر بودن تنظیمات
        """
        # Validate against schema if registered
        if settings_schema_registry.has_schema(module_key):
            errors = settings_schema_registry.validate_data(module_key, settings_data)
            if errors:
                raise SettingsValidationError(errors=errors)

        obj, created = ModuleSettingsModel.all_tenants.update_or_create(
            tenant_id=tenant_id,
            module_key=module_key,
            defaults={
                "settings_data": settings_data,
                "updated_by_id": user_id,
            },
        )
        if created and user_id:
            obj.created_by_id = user_id
            obj.save(update_fields=["created_by_id"])

        # Invalidate cache
        try:
            cache.delete(_cache_key(tenant_id, module_key))
        except Exception:
            pass

        return obj.settings_data

    @staticmethod
    @transaction.atomic
    def set_value(
        module_key: str,
        tenant_id: UUID,
        path: str,
        value: Any,
        user_id: Optional[UUID] = None,
    ) -> dict[str, Any]:
        """
        تغییر مقدار یک تنظیم خاص.

        Args:
            module_key: کلید ماژول
            tenant_id: شناسه tenant
            path: مسیر تنظیم
            value: مقدار جدید
            user_id: شناسه کاربر

        Returns:
            دیکشنری کامل تنظیمات

        Raises:
            InvalidSettingsPath: مسیر نامعتبر
            SettingsValidationError: مقدار نامعتبر
        """
        try:
            settings_path = SettingsPath(path=path)
        except ValueError:
            raise InvalidSettingsPath(path=path, module_key=module_key)

        # Get current data
        current_data = ModuleSettingsService.get(module_key, tenant_id)

        # Apply change
        entity = ModuleSettings(
            module_key=module_key,
            tenant_id=tenant_id,
            settings_data=dict(current_data),  # Copy
        )
        entity.set_value(settings_path, value)

        # Validate entire data
        if settings_schema_registry.has_schema(module_key):
            errors = settings_schema_registry.validate_data(
                module_key, entity.settings_data
            )
            if errors:
                raise SettingsValidationError(errors=errors)

        # Persist
        obj, _ = ModuleSettingsModel.all_tenants.update_or_create(
            tenant_id=tenant_id,
            module_key=module_key,
            defaults={
                "settings_data": entity.settings_data,
                "updated_by_id": user_id,
            },
        )

        # Invalidate cache
        try:
            cache.delete(_cache_key(tenant_id, module_key))
        except Exception:
            pass

        return obj.settings_data

    @staticmethod
    @transaction.atomic
    def save_section(
        module_key: str,
        tenant_id: UUID,
        section_key: str,
        section_data: dict[str, Any],
        user_id: Optional[UUID] = None,
    ) -> dict[str, Any]:
        """
        ذخیره تنظیمات یک بخش.

        Args:
            module_key: کلید ماژول
            tenant_id: شناسه tenant
            section_key: کلید بخش
            section_data: دیکشنری تنظیمات بخش
            user_id: شناسه کاربر

        Returns:
            دیکشنری کامل تنظیمات
        """
        current_data = ModuleSettingsService.get(module_key, tenant_id)
        current_data[section_key] = section_data

        return ModuleSettingsService.save_settings(
            module_key=module_key,
            tenant_id=tenant_id,
            settings_data=current_data,
            user_id=user_id,
        )

    @staticmethod
    @transaction.atomic
    def reset_to_defaults(
        module_key: str,
        tenant_id: UUID,
        user_id: Optional[UUID] = None,
    ) -> dict[str, Any]:
        """
        بازنشانی تنظیمات به مقادیر پیش‌فرض.

        Args:
            module_key: کلید ماژول
            tenant_id: شناسه tenant
            user_id: شناسه کاربر

        Returns:
            دیکشنری تنظیمات پیش‌فرض
        """
        defaults = {}
        if settings_schema_registry.has_schema(module_key):
            defaults = settings_schema_registry.get_defaults(module_key)

        return ModuleSettingsService.save_settings(
            module_key=module_key,
            tenant_id=tenant_id,
            settings_data=defaults,
            user_id=user_id,
        )

    # ─── Schema ───────────────────────────────────────────

    @staticmethod
    def get_schema(module_key: str) -> dict[str, Any]:
        """
        دریافت schema تنظیمات یک ماژول.

        Args:
            module_key: کلید ماژول

        Returns:
            دیکشنری schema
        """
        return settings_schema_registry.schema_to_dict(module_key)

    @staticmethod
    def list_available_modules(tenant_id=None) -> list[dict]:
        """
        لیست ماژول‌های دارای تنظیمات.

        Returns:
            لیست دیکشنری‌های حاوی اطلاعات ماژول
        """
        # Find which modules have saved settings for this tenant
        saved_keys = set()
        if tenant_id:
            saved_keys = set(
                ModuleSettingsModel.all_tenants.filter(
                    tenant_id=tenant_id
                ).values_list("module_key", flat=True)
            )

        result = []
        for schema in settings_schema_registry.list_schemas():
            result.append(
                {
                    "module_key": schema.module_key,
                    "module_label": schema.label,
                    "label": schema.label,
                    "label_en": schema.label_en,
                    "description": schema.description,
                    "has_saved_settings": schema.module_key in saved_keys,
                }
            )
        return result

    # ─── Helper ───────────────────────────────────────────

    @staticmethod
    def _deep_merge_defaults(data: dict, defaults: dict) -> dict:
        """ادغام مقادیر پیش‌فرض — فقط کلیدهای ناموجود اضافه می‌شوند."""
        result = dict(data)
        for key, value in defaults.items():
            if key not in result:
                result[key] = value
            elif isinstance(result[key], dict) and isinstance(value, dict):
                result[key] = ModuleSettingsService._deep_merge_defaults(
                    result[key], value
                )
        return result
