"""Theme & layout engine repositories — Phase 11.3."""
from __future__ import annotations

from typing import Sequence
from uuid import UUID

from sqlalchemy import func, select

from app.models.themes import (
    ExperienceLayout,
    ExperienceLayoutVersion,
    ExperienceTheme,
    ExperienceThemeVersion,
    PageLayoutBinding,
    SiteThemeBinding,
)
from app.repositories.base import TenantBaseRepository
from app.specifications.themes import (
    LayoutListSpec,
    LayoutVersionListSpec,
    PageLayoutBindingListSpec,
    SiteThemeBindingListSpec,
    ThemeListSpec,
    ThemeVersionListSpec,
)


class ExperienceThemeRepository(TenantBaseRepository[ExperienceTheme]):
    model = ExperienceTheme

    async def get_by_code(
        self, tenant_id: UUID, workspace_id: UUID, code: str
    ) -> ExperienceTheme | None:
        stmt = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.workspace_id == workspace_id,
            self.model.code == code,
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()

    async def list_filtered(
        self,
        tenant_id: UUID,
        *,
        offset: int,
        limit: int,
        spec: ThemeListSpec | None = None,
    ) -> tuple[Sequence[ExperienceTheme], int]:
        base = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.is_deleted.is_(False),
        )
        count_base = select(func.count()).select_from(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.is_deleted.is_(False),
        )
        if spec is not None:
            base = spec.apply(base)
            for clause in spec.filter_clauses():
                count_base = count_base.where(clause)
        else:
            base = base.order_by(self.model.created_at.desc())
        total = int((await self.session.execute(count_base)).scalar_one())
        result = await self.session.execute(base.offset(offset).limit(limit))
        return result.scalars().all(), total


class ExperienceThemeVersionRepository(TenantBaseRepository[ExperienceThemeVersion]):
    model = ExperienceThemeVersion

    async def get_by_number(
        self, tenant_id: UUID, theme_id: UUID, version_number: int
    ) -> ExperienceThemeVersion | None:
        stmt = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.theme_id == theme_id,
            self.model.version_number == version_number,
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()

    async def max_version_number(self, tenant_id: UUID, theme_id: UUID) -> int:
        stmt = select(func.max(self.model.version_number)).where(
            self.model.tenant_id == tenant_id,
            self.model.theme_id == theme_id,
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        value = result.scalar_one()
        return int(value or 0)

    async def list_filtered(
        self,
        tenant_id: UUID,
        *,
        offset: int,
        limit: int,
        spec: ThemeVersionListSpec | None = None,
    ) -> tuple[Sequence[ExperienceThemeVersion], int]:
        base = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.is_deleted.is_(False),
        )
        count_base = select(func.count()).select_from(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.is_deleted.is_(False),
        )
        if spec is not None:
            base = spec.apply(base)
            for clause in spec.filter_clauses():
                count_base = count_base.where(clause)
        else:
            base = base.order_by(self.model.version_number.desc())
        total = int((await self.session.execute(count_base)).scalar_one())
        result = await self.session.execute(base.offset(offset).limit(limit))
        return result.scalars().all(), total


class ExperienceLayoutRepository(TenantBaseRepository[ExperienceLayout]):
    model = ExperienceLayout

    async def get_by_code(
        self, tenant_id: UUID, workspace_id: UUID, code: str
    ) -> ExperienceLayout | None:
        stmt = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.workspace_id == workspace_id,
            self.model.code == code,
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()

    async def list_filtered(
        self,
        tenant_id: UUID,
        *,
        offset: int,
        limit: int,
        spec: LayoutListSpec | None = None,
    ) -> tuple[Sequence[ExperienceLayout], int]:
        base = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.is_deleted.is_(False),
        )
        count_base = select(func.count()).select_from(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.is_deleted.is_(False),
        )
        if spec is not None:
            base = spec.apply(base)
            for clause in spec.filter_clauses():
                count_base = count_base.where(clause)
        else:
            base = base.order_by(self.model.created_at.desc())
        total = int((await self.session.execute(count_base)).scalar_one())
        result = await self.session.execute(base.offset(offset).limit(limit))
        return result.scalars().all(), total


class ExperienceLayoutVersionRepository(TenantBaseRepository[ExperienceLayoutVersion]):
    model = ExperienceLayoutVersion

    async def get_by_number(
        self, tenant_id: UUID, layout_id: UUID, version_number: int
    ) -> ExperienceLayoutVersion | None:
        stmt = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.layout_id == layout_id,
            self.model.version_number == version_number,
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()

    async def max_version_number(self, tenant_id: UUID, layout_id: UUID) -> int:
        stmt = select(func.max(self.model.version_number)).where(
            self.model.tenant_id == tenant_id,
            self.model.layout_id == layout_id,
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        value = result.scalar_one()
        return int(value or 0)

    async def list_filtered(
        self,
        tenant_id: UUID,
        *,
        offset: int,
        limit: int,
        spec: LayoutVersionListSpec | None = None,
    ) -> tuple[Sequence[ExperienceLayoutVersion], int]:
        base = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.is_deleted.is_(False),
        )
        count_base = select(func.count()).select_from(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.is_deleted.is_(False),
        )
        if spec is not None:
            base = spec.apply(base)
            for clause in spec.filter_clauses():
                count_base = count_base.where(clause)
        else:
            base = base.order_by(self.model.version_number.desc())
        total = int((await self.session.execute(count_base)).scalar_one())
        result = await self.session.execute(base.offset(offset).limit(limit))
        return result.scalars().all(), total


class SiteThemeBindingRepository(TenantBaseRepository[SiteThemeBinding]):
    model = SiteThemeBinding

    async def get_active_for_site(
        self, tenant_id: UUID, site_id: UUID
    ) -> SiteThemeBinding | None:
        stmt = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.site_id == site_id,
            self.model.is_active.is_(True),
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()

    async def count_active_by_theme(self, tenant_id: UUID, theme_id: UUID) -> int:
        stmt = select(func.count()).select_from(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.theme_id == theme_id,
            self.model.is_active.is_(True),
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return int(result.scalar_one())

    async def count_active_by_version(
        self, tenant_id: UUID, theme_version_id: UUID
    ) -> int:
        stmt = select(func.count()).select_from(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.theme_version_id == theme_version_id,
            self.model.is_active.is_(True),
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return int(result.scalar_one())

    async def list_filtered(
        self,
        tenant_id: UUID,
        *,
        offset: int,
        limit: int,
        spec: SiteThemeBindingListSpec | None = None,
    ) -> tuple[Sequence[SiteThemeBinding], int]:
        base = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.is_deleted.is_(False),
        )
        count_base = select(func.count()).select_from(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.is_deleted.is_(False),
        )
        if spec is not None:
            base = spec.apply(base)
            for clause in spec.filter_clauses():
                count_base = count_base.where(clause)
        else:
            base = base.order_by(self.model.created_at.desc())
        total = int((await self.session.execute(count_base)).scalar_one())
        result = await self.session.execute(base.offset(offset).limit(limit))
        return result.scalars().all(), total


class PageLayoutBindingRepository(TenantBaseRepository[PageLayoutBinding]):
    model = PageLayoutBinding

    async def get_active_for_page(
        self, tenant_id: UUID, page_id: UUID
    ) -> PageLayoutBinding | None:
        stmt = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.page_id == page_id,
            self.model.is_active.is_(True),
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return result.scalar_one_or_none()

    async def count_active_by_layout(self, tenant_id: UUID, layout_id: UUID) -> int:
        stmt = select(func.count()).select_from(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.layout_id == layout_id,
            self.model.is_active.is_(True),
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return int(result.scalar_one())

    async def count_active_by_version(
        self, tenant_id: UUID, layout_version_id: UUID
    ) -> int:
        stmt = select(func.count()).select_from(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.layout_version_id == layout_version_id,
            self.model.is_active.is_(True),
            self.model.is_deleted.is_(False),
        )
        result = await self.session.execute(stmt)
        return int(result.scalar_one())

    async def list_filtered(
        self,
        tenant_id: UUID,
        *,
        offset: int,
        limit: int,
        spec: PageLayoutBindingListSpec | None = None,
    ) -> tuple[Sequence[PageLayoutBinding], int]:
        base = select(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.is_deleted.is_(False),
        )
        count_base = select(func.count()).select_from(self.model).where(
            self.model.tenant_id == tenant_id,
            self.model.is_deleted.is_(False),
        )
        if spec is not None:
            base = spec.apply(base)
            for clause in spec.filter_clauses():
                count_base = count_base.where(clause)
        else:
            base = base.order_by(self.model.created_at.desc())
        total = int((await self.session.execute(count_base)).scalar_one())
        result = await self.session.execute(base.offset(offset).limit(limit))
        return result.scalars().all(), total
