"""edge_functions domain — data access layer."""
from __future__ import annotations

from datetime import datetime, timezone
from typing import Optional
from uuid import UUID, uuid4

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from src.domain.edge_functions.models import (
    FunctionLibrary,
    FunctionLibraryVersion,
    ProjectFunction,
    ProjectFunctionToken,
    ProjectFunctionVersion,
    ProjectWebhookEndpoint,
    WorkerRuntimeVersion,
    WorkspaceRuntimeConfig,
)


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

    async def list_published(
        self,
        trigger_type: Optional[str] = None,
        tags: Optional[list[str]] = None,
        integration: Optional[str] = None,
        category: Optional[str] = None,
        minimum_plan: Optional[str] = None,
        visibility: Optional[str] = None,
    ) -> list[FunctionLibrary]:
        q = select(FunctionLibrary).where(FunctionLibrary.status == "published")
        if trigger_type:
            q = q.where(FunctionLibrary.trigger_type == trigger_type)
        if category:
            q = q.where(FunctionLibrary.category == category)
        if minimum_plan:
            q = q.where(FunctionLibrary.minimum_plan == minimum_plan)
        if visibility:
            q = q.where(FunctionLibrary.visibility == visibility)
        result = await self._db.execute(q.order_by(FunctionLibrary.category, FunctionLibrary.name))
        rows = list(result.scalars().all())
        if tags:
            rows = [r for r in rows if r.tags and any(t in r.tags for t in tags)]
        if integration:
            rows = [
                r for r in rows
                if r.required_integrations and integration in r.required_integrations
            ]
        return rows

    async def list_all(self) -> list[FunctionLibrary]:
        result = await self._db.execute(
            select(FunctionLibrary).order_by(FunctionLibrary.category, FunctionLibrary.name)
        )
        return list(result.scalars().all())

    # keep backward-compat alias used by existing service code
    async def list_active(
        self,
        trigger_type: Optional[str] = None,
        tags: Optional[list[str]] = None,
        integration: Optional[str] = None,
    ) -> list[FunctionLibrary]:
        return await self.list_published(
            trigger_type=trigger_type, tags=tags, integration=integration
        )

    async def update(self, row: FunctionLibrary, **fields) -> FunctionLibrary:
        for k, v in fields.items():
            setattr(row, k, v)
        await self._db.flush()
        await self._db.refresh(row)
        return row

    async def increment_install_count(self, fn_id: UUID) -> None:
        from sqlalchemy import update
        await self._db.execute(
            update(FunctionLibrary)
            .where(FunctionLibrary.id == fn_id)
            .values(install_count=FunctionLibrary.install_count + 1)
        )

    async def get_by_id(self, fn_id: UUID) -> Optional[FunctionLibrary]:
        result = await self._db.execute(
            select(FunctionLibrary).where(FunctionLibrary.id == fn_id)
        )
        return result.scalar_one_or_none()

    async def get_by_slug(self, slug: str) -> Optional[FunctionLibrary]:
        result = await self._db.execute(
            select(FunctionLibrary).where(FunctionLibrary.slug == slug)
        )
        return result.scalar_one_or_none()

    async def create(self, **fields) -> FunctionLibrary:
        row = FunctionLibrary(id=uuid4(), **fields)
        self._db.add(row)
        await self._db.flush()
        return row


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

    async def list_for_project(self, project_id: UUID) -> list[ProjectFunction]:
        result = await self._db.execute(
            select(ProjectFunction)
            .where(
                ProjectFunction.project_id == project_id,
                ProjectFunction.deleted_at.is_(None),
            )
            .order_by(ProjectFunction.created_at)
        )
        return list(result.scalars().all())

    async def count_active(self, project_id: UUID) -> int:
        from sqlalchemy import func

        result = await self._db.execute(
            select(func.count())
            .select_from(ProjectFunction)
            .where(
                ProjectFunction.project_id == project_id,
                ProjectFunction.deleted_at.is_(None),
                ProjectFunction.status != "disabled",
            )
        )
        return result.scalar_one()

    async def get_by_slug(
        self, project_id: UUID, slug: str
    ) -> Optional[ProjectFunction]:
        result = await self._db.execute(
            select(ProjectFunction).where(
                ProjectFunction.project_id == project_id,
                ProjectFunction.slug == slug,
                ProjectFunction.deleted_at.is_(None),
            )
        )
        return result.scalar_one_or_none()

    async def list_by_pack(self, project_id: UUID, pack_slug: str) -> list[ProjectFunction]:
        result = await self._db.execute(
            select(ProjectFunction).where(
                ProjectFunction.project_id == project_id,
                ProjectFunction.pack_slug == pack_slug,
                ProjectFunction.deleted_at.is_(None),
            )
        )
        return list(result.scalars().all())

    async def create(self, **fields) -> ProjectFunction:
        row = ProjectFunction(id=uuid4(), **fields)
        self._db.add(row)
        await self._db.flush()
        return row

    async def update(self, row: ProjectFunction, **fields) -> ProjectFunction:
        for k, v in fields.items():
            setattr(row, k, v)
        await self._db.flush()
        await self._db.refresh(row)
        return row

    async def soft_delete(self, row: ProjectFunction) -> None:
        row.deleted_at = datetime.now(timezone.utc)
        row.status = "disabled"
        await self._db.flush()


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

    async def list_for_function(self, function_id: UUID) -> list[ProjectFunctionVersion]:
        result = await self._db.execute(
            select(ProjectFunctionVersion)
            .where(ProjectFunctionVersion.function_id == function_id)
            .order_by(ProjectFunctionVersion.version_number.desc())
        )
        return list(result.scalars().all())

    async def get_latest_pending(
        self, function_id: UUID
    ) -> Optional[ProjectFunctionVersion]:
        result = await self._db.execute(
            select(ProjectFunctionVersion)
            .where(
                ProjectFunctionVersion.function_id == function_id,
                ProjectFunctionVersion.status == "pending",
            )
            .order_by(ProjectFunctionVersion.version_number.desc())
            .limit(1)
        )
        return result.scalar_one_or_none()

    async def get_latest_deployed(
        self, function_id: UUID
    ) -> Optional[ProjectFunctionVersion]:
        result = await self._db.execute(
            select(ProjectFunctionVersion)
            .where(
                ProjectFunctionVersion.function_id == function_id,
                ProjectFunctionVersion.status == "deployed",
            )
            .order_by(ProjectFunctionVersion.version_number.desc())
            .limit(1)
        )
        return result.scalar_one_or_none()

    async def get_previous_deployed(
        self, function_id: UUID, exclude_version: int
    ) -> Optional[ProjectFunctionVersion]:
        result = await self._db.execute(
            select(ProjectFunctionVersion)
            .where(
                ProjectFunctionVersion.function_id == function_id,
                ProjectFunctionVersion.status == "deployed",
                ProjectFunctionVersion.version_number < exclude_version,
            )
            .order_by(ProjectFunctionVersion.version_number.desc())
            .limit(1)
        )
        return result.scalar_one_or_none()

    async def next_version_number(self, function_id: UUID) -> int:
        from sqlalchemy import func

        result = await self._db.execute(
            select(func.max(ProjectFunctionVersion.version_number)).where(
                ProjectFunctionVersion.function_id == function_id
            )
        )
        current_max = result.scalar_one_or_none()
        return (current_max or 0) + 1

    async def create(self, **fields) -> ProjectFunctionVersion:
        row = ProjectFunctionVersion(id=uuid4(), **fields)
        self._db.add(row)
        await self._db.flush()
        return row

    async def update(self, row: ProjectFunctionVersion, **fields) -> ProjectFunctionVersion:
        for k, v in fields.items():
            setattr(row, k, v)
        await self._db.flush()
        return row


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

    async def list_for_project(self, project_id: UUID) -> list[ProjectWebhookEndpoint]:
        result = await self._db.execute(
            select(ProjectWebhookEndpoint)
            .where(ProjectWebhookEndpoint.project_id == project_id)
            .order_by(ProjectWebhookEndpoint.created_at)
        )
        return list(result.scalars().all())

    async def get_by_token(self, wh_token: str) -> Optional[ProjectWebhookEndpoint]:
        result = await self._db.execute(
            select(ProjectWebhookEndpoint).where(
                ProjectWebhookEndpoint.wh_token == wh_token
            )
        )
        return result.scalar_one_or_none()

    async def get_by_provider(
        self, project_id: UUID, provider: str
    ) -> Optional[ProjectWebhookEndpoint]:
        result = await self._db.execute(
            select(ProjectWebhookEndpoint).where(
                ProjectWebhookEndpoint.project_id == project_id,
                ProjectWebhookEndpoint.provider == provider,
                ProjectWebhookEndpoint.is_active.is_(True),
            )
        )
        return result.scalar_one_or_none()

    async def create(self, **fields) -> ProjectWebhookEndpoint:
        row = ProjectWebhookEndpoint(id=uuid4(), **fields)
        self._db.add(row)
        await self._db.flush()
        return row

    async def update(self, row: ProjectWebhookEndpoint, **fields) -> ProjectWebhookEndpoint:
        for k, v in fields.items():
            setattr(row, k, v)
        await self._db.flush()
        await self._db.refresh(row)
        return row


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

    async def get(self, project_id: UUID) -> Optional[ProjectFunctionToken]:
        result = await self._db.execute(
            select(ProjectFunctionToken).where(ProjectFunctionToken.project_id == project_id)
        )
        return result.scalar_one_or_none()

    async def create(self, project_id: UUID, token_enc: str) -> ProjectFunctionToken:
        row = ProjectFunctionToken(project_id=project_id, token_enc=token_enc)
        self._db.add(row)
        await self._db.flush()
        return row

    async def update(self, row: ProjectFunctionToken, **fields) -> ProjectFunctionToken:
        for k, v in fields.items():
            setattr(row, k, v)
        await self._db.flush()
        return row


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

    async def list_all(self) -> list[WorkerRuntimeVersion]:
        result = await self._db.execute(
            select(WorkerRuntimeVersion).order_by(WorkerRuntimeVersion.created_at.desc())
        )
        return list(result.scalars().all())

    async def get_by_version(self, version: str) -> Optional[WorkerRuntimeVersion]:
        result = await self._db.execute(
            select(WorkerRuntimeVersion).where(WorkerRuntimeVersion.version == version)
        )
        return result.scalar_one_or_none()

    async def get_latest_stable(self) -> Optional[WorkerRuntimeVersion]:
        result = await self._db.execute(
            select(WorkerRuntimeVersion)
            .where(WorkerRuntimeVersion.status == "stable")
            .order_by(WorkerRuntimeVersion.released_at.desc())
            .limit(1)
        )
        return result.scalar_one_or_none()

    async def create(self, **fields) -> WorkerRuntimeVersion:
        row = WorkerRuntimeVersion(id=uuid4(), **fields)
        self._db.add(row)
        await self._db.flush()
        return row

    async def update(self, row: WorkerRuntimeVersion, **fields) -> WorkerRuntimeVersion:
        for k, v in fields.items():
            setattr(row, k, v)
        await self._db.flush()
        return row


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

    async def list_for_function(self, library_function_id: UUID) -> list[FunctionLibraryVersion]:
        result = await self._db.execute(
            select(FunctionLibraryVersion)
            .where(FunctionLibraryVersion.library_function_id == library_function_id)
            .order_by(FunctionLibraryVersion.created_at.desc())
        )
        return list(result.scalars().all())

    async def get_by_id(self, version_id: UUID) -> Optional[FunctionLibraryVersion]:
        result = await self._db.execute(
            select(FunctionLibraryVersion).where(FunctionLibraryVersion.id == version_id)
        )
        return result.scalar_one_or_none()

    async def get_by_version(
        self, library_function_id: UUID, version: str
    ) -> Optional[FunctionLibraryVersion]:
        result = await self._db.execute(
            select(FunctionLibraryVersion).where(
                FunctionLibraryVersion.library_function_id == library_function_id,
                FunctionLibraryVersion.version == version,
            )
        )
        return result.scalar_one_or_none()

    async def create(self, **fields) -> FunctionLibraryVersion:
        row = FunctionLibraryVersion(id=uuid4(), **fields)
        self._db.add(row)
        await self._db.flush()
        await self._db.refresh(row)
        return row

    async def update(self, row: FunctionLibraryVersion, **fields) -> FunctionLibraryVersion:
        for k, v in fields.items():
            setattr(row, k, v)
        await self._db.flush()
        return row
