"""Theme & layout query specifications — Phase 11.3 (versioned)."""
from __future__ import annotations

from dataclasses import dataclass
from uuid import UUID

from sqlalchemy import Select, asc, desc, or_
from sqlalchemy.sql import ColumnElement

from app.models.themes import (
    ExperienceLayout,
    ExperienceLayoutVersion,
    ExperienceTheme,
    ExperienceThemeVersion,
    PageLayoutBinding,
    SiteThemeBinding,
)
from app.models.types import (
    DirectionMode,
    LayoutKind,
    LayoutStatus,
    LayoutVersionStatus,
    ThemeStatus,
    ThemeVersionStatus,
)

THEME_SORT_FIELDS = frozenset(
    {"created_at", "updated_at", "code", "name", "status", "direction_mode"}
)
THEME_VERSION_SORT_FIELDS = frozenset(
    {"created_at", "updated_at", "version_number", "status"}
)
LAYOUT_SORT_FIELDS = frozenset(
    {"created_at", "updated_at", "code", "name", "status", "direction_mode", "kind"}
)
LAYOUT_VERSION_SORT_FIELDS = frozenset(
    {"created_at", "updated_at", "version_number", "status"}
)
SITE_THEME_BINDING_SORT_FIELDS = frozenset({"created_at", "updated_at"})
PAGE_LAYOUT_BINDING_SORT_FIELDS = frozenset({"created_at", "updated_at"})


@dataclass(frozen=True, slots=True)
class ThemeListSpec:
    workspace_id: UUID | None = None
    status: ThemeStatus | str | None = None
    direction_mode: DirectionMode | str | None = None
    q: str | None = None
    sort_by: str = "created_at"
    sort_dir: str = "desc"

    def filter_clauses(self) -> list[ColumnElement]:
        clauses: list[ColumnElement] = []
        if self.workspace_id is not None:
            clauses.append(ExperienceTheme.workspace_id == self.workspace_id)
        if self.status is not None:
            status = (
                self.status
                if isinstance(self.status, ThemeStatus)
                else ThemeStatus(str(self.status))
            )
            clauses.append(ExperienceTheme.status == status)
        if self.direction_mode is not None:
            mode = (
                self.direction_mode
                if isinstance(self.direction_mode, DirectionMode)
                else DirectionMode(str(self.direction_mode))
            )
            clauses.append(ExperienceTheme.direction_mode == mode)
        if self.q:
            term = f"%{self.q.strip()}%"
            clauses.append(
                or_(
                    ExperienceTheme.code.ilike(term),
                    ExperienceTheme.name.ilike(term),
                    ExperienceTheme.description.ilike(term),
                )
            )
        return clauses

    def apply(self, stmt: Select) -> Select:
        clauses = self.filter_clauses()
        if clauses:
            stmt = stmt.where(*clauses)
        sort_key = self.sort_by if self.sort_by in THEME_SORT_FIELDS else "created_at"
        column = getattr(ExperienceTheme, sort_key)
        order = desc(column) if self.sort_dir.lower() != "asc" else asc(column)
        return stmt.order_by(order)


@dataclass(frozen=True, slots=True)
class ThemeVersionListSpec:
    theme_id: UUID | None = None
    status: ThemeVersionStatus | str | None = None
    sort_by: str = "version_number"
    sort_dir: str = "desc"

    def filter_clauses(self) -> list[ColumnElement]:
        clauses: list[ColumnElement] = []
        if self.theme_id is not None:
            clauses.append(ExperienceThemeVersion.theme_id == self.theme_id)
        if self.status is not None:
            status = (
                self.status
                if isinstance(self.status, ThemeVersionStatus)
                else ThemeVersionStatus(str(self.status))
            )
            clauses.append(ExperienceThemeVersion.status == status)
        return clauses

    def apply(self, stmt: Select) -> Select:
        clauses = self.filter_clauses()
        if clauses:
            stmt = stmt.where(*clauses)
        sort_key = (
            self.sort_by if self.sort_by in THEME_VERSION_SORT_FIELDS else "version_number"
        )
        column = getattr(ExperienceThemeVersion, sort_key)
        order = desc(column) if self.sort_dir.lower() != "asc" else asc(column)
        return stmt.order_by(order)


@dataclass(frozen=True, slots=True)
class LayoutListSpec:
    workspace_id: UUID | None = None
    status: LayoutStatus | str | None = None
    direction_mode: DirectionMode | str | None = None
    kind: LayoutKind | str | None = None
    q: str | None = None
    sort_by: str = "created_at"
    sort_dir: str = "desc"

    def filter_clauses(self) -> list[ColumnElement]:
        clauses: list[ColumnElement] = []
        if self.workspace_id is not None:
            clauses.append(ExperienceLayout.workspace_id == self.workspace_id)
        if self.status is not None:
            status = (
                self.status
                if isinstance(self.status, LayoutStatus)
                else LayoutStatus(str(self.status))
            )
            clauses.append(ExperienceLayout.status == status)
        if self.direction_mode is not None:
            mode = (
                self.direction_mode
                if isinstance(self.direction_mode, DirectionMode)
                else DirectionMode(str(self.direction_mode))
            )
            clauses.append(ExperienceLayout.direction_mode == mode)
        if self.kind is not None:
            kind = (
                self.kind if isinstance(self.kind, LayoutKind) else LayoutKind(str(self.kind))
            )
            clauses.append(ExperienceLayout.kind == kind)
        if self.q:
            term = f"%{self.q.strip()}%"
            clauses.append(
                or_(
                    ExperienceLayout.code.ilike(term),
                    ExperienceLayout.name.ilike(term),
                    ExperienceLayout.description.ilike(term),
                )
            )
        return clauses

    def apply(self, stmt: Select) -> Select:
        clauses = self.filter_clauses()
        if clauses:
            stmt = stmt.where(*clauses)
        sort_key = (
            self.sort_by if self.sort_by in LAYOUT_SORT_FIELDS else "created_at"
        )
        column = getattr(ExperienceLayout, sort_key)
        order = desc(column) if self.sort_dir.lower() != "asc" else asc(column)
        return stmt.order_by(order)


@dataclass(frozen=True, slots=True)
class LayoutVersionListSpec:
    layout_id: UUID | None = None
    status: LayoutVersionStatus | str | None = None
    sort_by: str = "version_number"
    sort_dir: str = "desc"

    def filter_clauses(self) -> list[ColumnElement]:
        clauses: list[ColumnElement] = []
        if self.layout_id is not None:
            clauses.append(ExperienceLayoutVersion.layout_id == self.layout_id)
        if self.status is not None:
            status = (
                self.status
                if isinstance(self.status, LayoutVersionStatus)
                else LayoutVersionStatus(str(self.status))
            )
            clauses.append(ExperienceLayoutVersion.status == status)
        return clauses

    def apply(self, stmt: Select) -> Select:
        clauses = self.filter_clauses()
        if clauses:
            stmt = stmt.where(*clauses)
        sort_key = (
            self.sort_by
            if self.sort_by in LAYOUT_VERSION_SORT_FIELDS
            else "version_number"
        )
        column = getattr(ExperienceLayoutVersion, sort_key)
        order = desc(column) if self.sort_dir.lower() != "asc" else asc(column)
        return stmt.order_by(order)


@dataclass(frozen=True, slots=True)
class SiteThemeBindingListSpec:
    site_id: UUID | None = None
    theme_id: UUID | None = None
    theme_version_id: UUID | None = None
    workspace_id: UUID | None = None
    is_active: bool | None = None
    sort_by: str = "created_at"
    sort_dir: str = "desc"

    def filter_clauses(self) -> list[ColumnElement]:
        clauses: list[ColumnElement] = []
        if self.site_id is not None:
            clauses.append(SiteThemeBinding.site_id == self.site_id)
        if self.theme_id is not None:
            clauses.append(SiteThemeBinding.theme_id == self.theme_id)
        if self.theme_version_id is not None:
            clauses.append(SiteThemeBinding.theme_version_id == self.theme_version_id)
        if self.workspace_id is not None:
            clauses.append(SiteThemeBinding.workspace_id == self.workspace_id)
        if self.is_active is not None:
            clauses.append(SiteThemeBinding.is_active.is_(self.is_active))
        return clauses

    def apply(self, stmt: Select) -> Select:
        clauses = self.filter_clauses()
        if clauses:
            stmt = stmt.where(*clauses)
        sort_key = (
            self.sort_by
            if self.sort_by in SITE_THEME_BINDING_SORT_FIELDS
            else "created_at"
        )
        column = getattr(SiteThemeBinding, sort_key)
        order = desc(column) if self.sort_dir.lower() != "asc" else asc(column)
        return stmt.order_by(order)


@dataclass(frozen=True, slots=True)
class PageLayoutBindingListSpec:
    page_id: UUID | None = None
    layout_id: UUID | None = None
    layout_version_id: UUID | None = None
    site_id: UUID | None = None
    workspace_id: UUID | None = None
    is_active: bool | None = None
    sort_by: str = "created_at"
    sort_dir: str = "desc"

    def filter_clauses(self) -> list[ColumnElement]:
        clauses: list[ColumnElement] = []
        if self.page_id is not None:
            clauses.append(PageLayoutBinding.page_id == self.page_id)
        if self.layout_id is not None:
            clauses.append(PageLayoutBinding.layout_id == self.layout_id)
        if self.layout_version_id is not None:
            clauses.append(PageLayoutBinding.layout_version_id == self.layout_version_id)
        if self.site_id is not None:
            clauses.append(PageLayoutBinding.site_id == self.site_id)
        if self.workspace_id is not None:
            clauses.append(PageLayoutBinding.workspace_id == self.workspace_id)
        if self.is_active is not None:
            clauses.append(PageLayoutBinding.is_active.is_(self.is_active))
        return clauses

    def apply(self, stmt: Select) -> Select:
        clauses = self.filter_clauses()
        if clauses:
            stmt = stmt.where(*clauses)
        sort_key = (
            self.sort_by
            if self.sort_by in PAGE_LAYOUT_BINDING_SORT_FIELDS
            else "created_at"
        )
        column = getattr(PageLayoutBinding, sort_key)
        order = desc(column) if self.sort_dir.lower() != "asc" else asc(column)
        return stmt.order_by(order)
