from __future__ import annotations

from datetime import UTC, datetime
from unittest.mock import AsyncMock

import pytest
from aiogram.types import Chat, Message, User

from app.errors import PermissionDeniedError, ValidationError
from app.middlewares.access import AdminOnlyMiddleware
from tests.helpers import create_ready_post, publish_post


@pytest.mark.asyncio
async def test_admin_seed_primary_protection_and_audit(ctx):
    assert await ctx.admins.is_admin(111_000_001) is True
    assert await ctx.admins.is_admin(222_000_002) is True
    assert await ctx.admins.is_primary(111_000_001) is True
    assert await ctx.admins.is_primary(222_000_002) is False
    assert await ctx.admins.is_admin(333_000_003) is False

    with pytest.raises(PermissionDeniedError):
        await ctx.admins.add(222_000_002, "333000003", None)

    admin, created = await ctx.admins.add(111_000_001, "333000003", "DB admin")
    assert created is True and admin.is_env_admin is False
    assert await ctx.admins.is_admin(333_000_003) is True

    with pytest.raises(ValidationError):
        await ctx.admins.remove(111_000_001, 111_000_001)

    # env admins cannot be removed via the panel
    message = await ctx.admins.remove(111_000_001, 222_000_002)
    assert "ADMIN_IDS" in message
    assert await ctx.admins.is_admin(222_000_002) is True

    message = await ctx.admins.remove(111_000_001, 333_000_003)
    assert "غیرفعال" in message
    assert await ctx.admins.is_admin(333_000_003) is False

    logs = await ctx.repos.audit.list_recent(20)
    actions = {log.action for log in logs}
    assert "admin.add" in actions
    assert "admin.remove" in actions


@pytest.mark.asyncio
async def test_env_admin_resync_moves_primary_and_deactivates_removed(ctx):
    await ctx.admins.seed([222_000_002, 444_000_004])
    assert await ctx.admins.is_primary(222_000_002) is True
    assert await ctx.admins.is_primary(111_000_001) is False
    assert await ctx.admins.is_admin(111_000_001) is False
    assert await ctx.admins.is_admin(444_000_004) is True


@pytest.mark.asyncio
async def test_admin_only_middleware_checks_database_on_every_update(ctx, monkeypatch):
    message = Message.model_construct(
        message_id=1,
        date=datetime.now(UTC),
        chat=Chat(id=111_000_001, type="private"),
        from_user=User(id=111_000_001, is_bot=False, first_name="Admin"),
        text="/admin",
    )
    handler = AsyncMock(return_value="ok")
    middleware = AdminOnlyMiddleware(ctx.admins)
    result = await middleware(handler, message, {})
    assert result == "ok"
    handler.assert_awaited_once()

    outsider = Message.model_construct(
        message_id=2,
        date=datetime.now(UTC),
        chat=Chat(id=999, type="private"),
        from_user=User(id=999, is_bot=False, first_name="Outsider"),
        text="/admin",
    )
    answer = AsyncMock()
    monkeypatch.setattr(Message, "answer", answer)
    result = await middleware(handler, outsider, {})
    assert result is None
    answer.assert_awaited_once()
    assert "دسترسی" in answer.await_args.args[0]


@pytest.mark.asyncio
async def test_statistics_dashboard_uses_durable_counts(ctx):
    post = await create_ready_post(ctx)
    await publish_post(ctx, post.id)
    await ctx.repos.users.get_or_create(555, username="u", display_name="User")
    dashboard = await ctx.statistics.dashboard()
    assert dashboard.media_total >= 1
    assert dashboard.posts_published >= 1
    assert dashboard.users_total >= 1
    assert dashboard.required_channels == 0
    assert isinstance(dashboard.periods, dict)


@pytest.mark.asyncio
async def test_audit_log_records_actor_and_correlation(ctx):
    await ctx.repos.audit.log(
        111_000_001,
        "test.action",
        entity_type="post",
        entity_id=42,
        details={"x": 1},
        correlation_id="corr-1",
    )
    logs = await ctx.repos.audit.list_recent(10)
    assert logs[0].actor_id == 111_000_001
    assert logs[0].entity_id == "42"
    assert logs[0].details == {"x": 1}
    assert logs[0].correlation_id == "corr-1"
