from datetime import date
from typing import Optional

from sqlalchemy import func, select, text, update
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import joinedload


from src.db.models import Vsp
from src.domain.schemas import VSPCreate


class VSPRepository:
    def __init__(self, db: AsyncSession):
        self.db = db

    async def get(
            self, 
            vsp_id: int, 
            is_active: bool | None = None, 
            load_org_unit: bool = False,
            with_deleted: bool = False,
    ) -> Optional[Vsp]:
        queries = [Vsp.id == vsp_id, Vsp.is_deleted.is_(with_deleted)]
        if is_active is not None:
            queries.append(Vsp.is_active == is_active)
        if load_org_unit:
            return (
                (await self.db.execute(
                    select(Vsp).where(*queries).options(joinedload(Vsp.org_unit)).limit(1))
                ).scalars().first()
            )
        else:
            return (
                (await self.db.execute(select(Vsp).where(*queries).limit(1))).scalars().first()
            )

    async def get_list(
        self,
        registration_number: str | None = None,
        address: str | None = None,
        ssp_ids: list[int] | None = None,
        open_date_start: date | None = None,
        open_date_end: date | None = None,
        placement_type: str | None = None,
        staff_count_min: int | None = None,
        staff_count_max: int | None = None,
        is_active: bool | None = True,
        close_date_is_null: bool | None = None,
        load_org_unit: bool = False,
        with_count: bool = False,
    ) -> list[Vsp] | tuple[int, list[Vsp]]:
        
        if with_count:
            query = select(func.count().over().label("total_count"), Vsp)
        else:
            query = select(Vsp)
        queries = [Vsp.is_deleted.is_(False)]
        filters = [
            ("registration_number", registration_number),
            ("address", address),
            ("placement_type", placement_type),
            ("open_date_start", open_date_start),
            ("open_date_end", open_date_end),
            ("staff_count_min", staff_count_min),
            ("staff_count_max", staff_count_max),
            ("is_active", is_active),
        ]
        for filter_name, filter_value in filters:
            # print(filter_name, filter_value)
            match filter_name, filter_value:
                case ("registration_number", str(value)) if value:
                    queries.append(Vsp.reg_number.ilike(f"%{value}%"))
                case ("address", str(value)) if value:
                    queries.append(Vsp.address.ilike(f"%{value}%"))
                case ("placement_type", str(value)) if value:
                    queries.append(Vsp.placement_type.ilike(f"%{value}%"))
                case ("open_date_start", value) if value is not None:
                    queries.append(Vsp.opened_at >= value)
                case ("open_date_end", value) if value is not None:
                    queries.append(Vsp.opened_at <= value)
                case ("staff_count_min", value) if value is not None:
                    queries.append(Vsp.staff_count >= value)
                case ("staff_count_max", value) if value is not None:
                    queries.append(Vsp.staff_count <= value)
                case ("is_active", value) if value is not None:
                    queries.append(Vsp.is_active == value)
                case _:
                    continue

        if close_date_is_null is True:
            queries.append(Vsp.closed_at.is_(None))
        elif close_date_is_null is False:
            queries.append(Vsp.closed_at.is_not(None))

        if ssp_ids is not None:
            if not ssp_ids:
                if with_count:
                    return 0, []
                return []
            queries.append(Vsp.branch_id.in_(ssp_ids))

        query = query.where(*queries)
        if load_org_unit:
            query = query.options(joinedload(Vsp.org_unit))
        query = query.order_by(Vsp.id)
        
        if with_count:
            result = (await self.db.execute(query)).all()
            if result:
                return result[0][0], [res[1] for res in result]
            else:
                count_query = select(func.count(Vsp.id))
                if query.whereclause is not None:
                    count_query = count_query.where(
                        query.whereclause
                    )
                return (await self.db.execute(count_query)).scalar(), []
        else:
            return (await self.db.execute(query)).scalars().all()
        return (await self.db.execute(query.order_by(Vsp.id))).scalars().all()

    async def create(self, payload: VSPCreate, created_by: int, load_org_unit: bool = False) -> Vsp:
        data = payload.model_dump()

        vsp_id = (
            await self.db.execute(
                text("""
                    SELECT v3.add_vsp(
                        :p_branch_id, :p_reg_number, :p_address, :p_format,
                        :p_opened_at, :p_placement_type, :p_staff_count,
                        :p_total_area, :p_closed_at, :p_is_active, :p_is_deleted,
                        :p_system_code, :p_created_by, :p_vsp_type, :p_notes,
                        :p_rent_contract_num, :p_rent_end_date
                    )
                """),
                {
                    "p_branch_id": data.get("branch_id"),
                    "p_reg_number": data.get("reg_number"),
                    "p_address": data.get("address"),
                    "p_format": data.get("format"),
                    "p_opened_at": data.get("opened_at"),
                    "p_placement_type": data.get("placement_type"),
                    "p_staff_count": data.get("staff_count"),
                    "p_total_area": data.get("total_area"),
                    "p_closed_at": data.get("closed_at"),
                    "p_is_active": data.get("is_active", True),
                    "p_is_deleted": data.get("is_deleted", False),
                    "p_system_code": data.get("system_code"),
                    "p_created_by": created_by,
                    "p_vsp_type": data.get("vsp_type"),
                    "p_notes": data.get("notes"),
                    "p_rent_contract_num": data.get("rent_contract_num"),
                    "p_rent_end_date": data.get("rent_end_date"),
                }
            )
        ).scalar()

        await self.db.flush()

        if load_org_unit:
            return (
                await self.db.execute(
                    select(Vsp).where(Vsp.id == vsp_id).options(joinedload(Vsp.org_unit)).limit(1)
                )
            ).scalars().first()
        else:
            return (
                await self.db.execute(
                    select(Vsp).where(Vsp.id == vsp_id).limit(1)
                )
            ).scalars().first()

    async def update(self, vsp: Vsp, data: dict, updated_by: int, load_org_unit: bool = False) -> Vsp:
        FIELD_TO_PARAM = {
            "system_code": "p_system_code",
            "vsp_type": "p_vsp_type",
            "reg_number": "p_reg_number",
            "address": "p_address",
            "opened_at": "p_opened_at",
            "closed_at": "p_closed_at",
            "format": "p_format",
            "placement_type": "p_placement_type",
            "staff_count": "p_staff_count",
            "notes": "p_notes",
            "total_area": "p_total_area",
            "rent_contract_num": "p_rent_contract_num",
            "rent_end_date": "p_rent_end_date",
            "branch_id": "p_branch_id",
            "is_active": "p_is_active",
            "is_deleted": "p_is_deleted",
        }

        params = {"p_vsp_id": vsp.id, "p_updated_by": updated_by}
        for field, param in FIELD_TO_PARAM.items():
            params[param] = data.get(field)

        await self.db.execute(
            text("""
                SELECT v3.upd_vsp(
                    :p_vsp_id, :p_branch_id, :p_reg_number, :p_address,
                    :p_format, :p_opened_at, :p_placement_type, :p_staff_count,
                    :p_total_area, :p_closed_at, :p_is_active, :p_is_deleted,
                    :p_system_code, :p_updated_by, :p_vsp_type, :p_notes,
                    :p_rent_contract_num, :p_rent_end_date
                )
            """),
            params,
        )

        await self.db.flush()
        await self.db.refresh(vsp)

        if load_org_unit:
            return (
                await self.db.execute(
                    select(Vsp).where(Vsp.id == vsp.id).options(joinedload(Vsp.org_unit)).limit(1)
                )
            ).scalars().first()
        else:
            return vsp

    async def deactivate_by_close_date(self, as_of: date) -> int:
        result = await self.db.execute(
            update(Vsp)
            .where(
                Vsp.is_active.is_(True),
                Vsp.closed_at.is_not(None),
                Vsp.closed_at <= as_of,
            )
            .values(is_active=False, updated_at=func.now())
        )
        await self.db.commit()
        return result.rowcount or 0

    async def logical_delete(self, vsp_id: int, updated_by: int) -> bool:
        result = (
            await self.db.execute(
                text("SELECT v3.del_vsp(:p_vsp_id, :p_deleted_by)"),
                {"p_vsp_id": vsp_id, "p_deleted_by": updated_by},
            )
        ).scalar()
        await self.db.flush()
        return result is not None


