from __future__ import annotations

import uuid
from datetime import UTC, datetime
from types import SimpleNamespace
from typing import Any, cast

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

from tracecat.auth.types import Role
from tracecat.db.models import ServiceAccount, ServiceAccountApiKey
from tracecat.pagination import BaseCursorPaginator, CursorPaginationParams
from tracecat.service_accounts.service import (
    OrganizationServiceAccountService,
    _paginate_service_account_api_keys,
    _paginate_service_accounts,
)
from tracecat.tiers import defaults as tier_defaults


@pytest.fixture(autouse=True)
def enable_service_accounts_entitlement(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(
        tier_defaults,
        "DEFAULT_ENTITLEMENTS",
        tier_defaults.DEFAULT_ENTITLEMENTS.model_copy(
            update={"service_accounts": True}
        ),
    )


class _ScalarResult:
    def all(self) -> list[ServiceAccount]:
        return []


class _ExecuteResult:
    def scalars(self) -> _ScalarResult:
        return _ScalarResult()


class _ItemsScalarResult:
    def __init__(self, items: list[Any]) -> None:
        self._items = items

    def all(self) -> list[Any]:
        return self._items


class _ItemsExecuteResult:
    def __init__(self, items: list[Any]) -> None:
        self._items = items

    def scalars(self) -> _ItemsScalarResult:
        return _ItemsScalarResult(self._items)


class _RecordingSession:
    def __init__(self) -> None:
        self.last_stmt: Any = None
        self.last_scalar_stmt: Any = None

    async def execute(self, stmt: Any) -> _ExecuteResult:
        self.last_stmt = stmt
        return _ExecuteResult()

    async def scalar(self, stmt: Any) -> int:
        self.last_scalar_stmt = stmt
        return 7


class _ItemsSession:
    def __init__(self, items: list[Any]) -> None:
        self._items = items

    async def execute(self, stmt: Any) -> _ItemsExecuteResult:
        return _ItemsExecuteResult(self._items)


@pytest.mark.anyio
async def test_paginate_service_accounts_uses_ascending_order_for_reverse() -> None:
    session = _RecordingSession()

    await _paginate_service_accounts(
        cast(AsyncSession, session),
        stmt=select(ServiceAccount),
        params=CursorPaginationParams(limit=20, reverse=True),
        total_estimate=0,
    )

    compiled = str(session.last_stmt.compile())

    assert "service_account.created_at ASC" in compiled
    assert "service_account.id ASC" in compiled


@pytest.mark.anyio
async def test_paginate_service_account_api_keys_uses_ascending_order_for_reverse() -> (
    None
):
    session = _RecordingSession()

    await _paginate_service_account_api_keys(
        cast(AsyncSession, session),
        stmt=select(ServiceAccountApiKey),
        params=CursorPaginationParams(limit=20, reverse=True),
        total_estimate=0,
    )

    compiled = str(session.last_stmt.compile())

    assert "service_account_api_key.created_at ASC" in compiled
    assert "service_account_api_key.id ASC" in compiled


@pytest.mark.anyio
async def test_organization_list_uses_filtered_total_count() -> None:
    session = _RecordingSession()
    organization_id = uuid.uuid4()
    service = OrganizationServiceAccountService(
        cast(AsyncSession, session),
        role=Role(
            type="user",
            user_id=uuid.uuid4(),
            service_id="tracecat-api",
            organization_id=organization_id,
            scopes=frozenset({"org:service_account:read"}),
        ),
    )

    page = await service.list_service_accounts(CursorPaginationParams(limit=20))

    compiled_count = str(session.last_scalar_stmt.compile())
    assert "count(*)" in compiled_count.lower()
    assert "WHERE service_account.organization_id =" in compiled_count
    assert "service_account.workspace_id IS NULL" in compiled_count
    assert page.total_estimate == 7


@pytest.mark.anyio
async def test_paginate_service_accounts_swaps_flags_for_reverse() -> None:
    items = [
        SimpleNamespace(id=uuid.uuid4(), created_at=datetime(2024, 1, 3, tzinfo=UTC)),
        SimpleNamespace(id=uuid.uuid4(), created_at=datetime(2024, 1, 2, tzinfo=UTC)),
        SimpleNamespace(id=uuid.uuid4(), created_at=datetime(2024, 1, 1, tzinfo=UTC)),
    ]
    session = _ItemsSession(items)
    cursor = BaseCursorPaginator.encode_cursor(
        items[0].id,
        sort_column="created_at",
        sort_value=items[0].created_at,
    )

    page = await _paginate_service_accounts(
        cast(AsyncSession, session),
        stmt=select(ServiceAccount),
        params=CursorPaginationParams(limit=2, reverse=True, cursor=cursor),
        total_estimate=3,
    )

    assert len(page.items) == 2
    assert page.has_more is True
    assert page.has_previous is True


@pytest.mark.anyio
async def test_paginate_service_account_api_keys_swaps_flags_for_reverse() -> None:
    items = [
        SimpleNamespace(id=uuid.uuid4(), created_at=datetime(2024, 1, 3, tzinfo=UTC)),
        SimpleNamespace(id=uuid.uuid4(), created_at=datetime(2024, 1, 2, tzinfo=UTC)),
        SimpleNamespace(id=uuid.uuid4(), created_at=datetime(2024, 1, 1, tzinfo=UTC)),
    ]
    session = _ItemsSession(items)
    cursor = BaseCursorPaginator.encode_cursor(
        items[0].id,
        sort_column="created_at",
        sort_value=items[0].created_at,
    )

    page = await _paginate_service_account_api_keys(
        cast(AsyncSession, session),
        stmt=select(ServiceAccountApiKey),
        params=CursorPaginationParams(limit=2, reverse=True, cursor=cursor),
        total_estimate=3,
    )

    assert len(page.items) == 2
    assert page.has_more is True
    assert page.has_previous is True
