"""Commercial registry repository — generic CRUD for all kinds."""
from __future__ import annotations

import uuid
from datetime import datetime, timezone

from sqlalchemy import Select, and_, func, or_, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.models.commercial import CommercialRegistryKind, CommercialRegistryObject


class CommercialRegistryRepository:
    def __init__(self, db: AsyncSession) -> None:
        self.db = db

    async def list_kinds(self, *, enabled_only: bool = True) -> list[CommercialRegistryKind]:
        stmt: Select = select(CommercialRegistryKind).order_by(
            CommercialRegistryKind.sort_order, CommercialRegistryKind.kind
        )
        if enabled_only:
            stmt = stmt.where(CommercialRegistryKind.enabled.is_(True))
        return list((await self.db.execute(stmt)).scalars().all())

    async def get_kind(self, kind: str) -> CommercialRegistryKind | None:
        stmt = select(CommercialRegistryKind).where(CommercialRegistryKind.kind == kind)
        return (await self.db.execute(stmt)).scalar_one_or_none()

    async def get_kind_by_path(self, path: str) -> CommercialRegistryKind | None:
        stmt = select(CommercialRegistryKind).where(CommercialRegistryKind.path == path)
        return (await self.db.execute(stmt)).scalar_one_or_none()

    async def upsert_kind(self, kind: CommercialRegistryKind) -> CommercialRegistryKind:
        existing = await self.get_kind(kind.kind)
        if existing:
            existing.path = kind.path
            existing.display_name = kind.display_name
            existing.description = kind.description
            existing.code_field = kind.code_field
            existing.list_envelope = kind.list_envelope
            existing.public_read = kind.public_read
            existing.enabled = kind.enabled
            existing.sort_order = kind.sort_order
            existing.meta_data = kind.meta_data
            await self.db.flush()
            return existing
        self.db.add(kind)
        await self.db.flush()
        return kind

    def _base_query(
        self,
        kind: str,
        *,
        tenant_id: uuid.UUID | None,
        include_deleted: bool,
        status: str | None,
        visibility: str | None,
        q: str | None,
        platform_only: bool,
    ) -> Select:
        stmt = select(CommercialRegistryObject).where(CommercialRegistryObject.kind == kind)
        if not include_deleted:
            stmt = stmt.where(CommercialRegistryObject.is_deleted.is_(False))
        if status:
            stmt = stmt.where(CommercialRegistryObject.status == status)
        if visibility:
            stmt = stmt.where(CommercialRegistryObject.visibility == visibility)
        if platform_only:
            stmt = stmt.where(CommercialRegistryObject.tenant_id.is_(None))
        elif tenant_id is not None:
            stmt = stmt.where(
                or_(
                    CommercialRegistryObject.tenant_id.is_(None),
                    CommercialRegistryObject.tenant_id == tenant_id,
                )
            )
        if q:
            like = f"%{q}%"
            stmt = stmt.where(
                or_(
                    CommercialRegistryObject.display_name.ilike(like),
                    CommercialRegistryObject.stable_code.ilike(like),
                    CommercialRegistryObject.slug.ilike(like),
                    CommercialRegistryObject.description.ilike(like),
                )
            )
        return stmt

    async def list_objects(
        self,
        kind: str,
        *,
        tenant_id: uuid.UUID | None = None,
        include_deleted: bool = False,
        status: str | None = None,
        visibility: str | None = None,
        q: str | None = None,
        sort: str = "display_name",
        order: str = "asc",
        offset: int = 0,
        limit: int = 50,
        platform_only: bool = True,
    ) -> tuple[list[CommercialRegistryObject], int]:
        stmt = self._base_query(
            kind,
            tenant_id=tenant_id,
            include_deleted=include_deleted,
            status=status,
            visibility=visibility,
            q=q,
            platform_only=platform_only,
        )
        count_stmt = select(func.count()).select_from(stmt.subquery())
        total = int((await self.db.execute(count_stmt)).scalar_one())

        sort_col = getattr(CommercialRegistryObject, sort, CommercialRegistryObject.display_name)
        stmt = stmt.order_by(sort_col.desc() if order.lower() == "desc" else sort_col.asc())
        stmt = stmt.offset(offset).limit(limit)
        rows = list((await self.db.execute(stmt)).scalars().all())
        return rows, total

    async def get(self, object_id: uuid.UUID) -> CommercialRegistryObject | None:
        stmt = select(CommercialRegistryObject).where(CommercialRegistryObject.id == object_id)
        return (await self.db.execute(stmt)).scalar_one_or_none()

    async def get_by_code(
        self,
        kind: str,
        stable_code: str,
        *,
        version_code: str | None = None,
        tenant_id: uuid.UUID | None = None,
    ) -> CommercialRegistryObject | None:
        stmt = select(CommercialRegistryObject).where(
            and_(
                CommercialRegistryObject.kind == kind,
                CommercialRegistryObject.stable_code == stable_code,
                CommercialRegistryObject.is_deleted.is_(False),
            )
        )
        if version_code:
            stmt = stmt.where(CommercialRegistryObject.version_code == version_code)
        if tenant_id is None:
            stmt = stmt.where(CommercialRegistryObject.tenant_id.is_(None))
        else:
            stmt = stmt.where(
                or_(
                    CommercialRegistryObject.tenant_id == tenant_id,
                    CommercialRegistryObject.tenant_id.is_(None),
                )
            )
        stmt = stmt.order_by(CommercialRegistryObject.updated_at.desc())
        return (await self.db.execute(stmt)).scalars().first()

    async def create(self, obj: CommercialRegistryObject) -> CommercialRegistryObject:
        self.db.add(obj)
        await self.db.flush()
        return obj

    async def save(self, obj: CommercialRegistryObject) -> CommercialRegistryObject:
        await self.db.flush()
        return obj

    async def soft_delete(self, obj: CommercialRegistryObject, actor: uuid.UUID | None) -> None:
        now = datetime.now(timezone.utc)
        obj.is_deleted = True
        obj.deleted_at = now
        obj.status = "deleted"
        obj.updated_by = actor
        await self.db.flush()

    async def restore(self, obj: CommercialRegistryObject, actor: uuid.UUID | None) -> None:
        obj.is_deleted = False
        obj.deleted_at = None
        obj.status = "draft"
        obj.updated_by = actor
        await self.db.flush()
