import json
from datetime import datetime
from typing import Optional

import pytest
import pytest_asyncio
from fastapi import FastAPI
from sqlalchemy.sql import text
from sqlmodel import Field, SQLModel

from capebase.main import CapeBase
from capebase.models import FROM_AUTH_ID, AuthContext


# Test model
class AuditItem(SQLModel, table=True):
    __table_args__ = {'extend_existing': True}

    id: Optional[int] = Field(default=None, primary_key=True)
    user_id: FROM_AUTH_ID
    name: str
    created_at: datetime = Field(default_factory=datetime.utcnow)

@pytest_asyncio.fixture(scope="function")
def app():
    return FastAPI()

async def get_user_context():
    return AuthContext(id="test_user")

@pytest_asyncio.fixture(scope="function")
async def cape(app):
    cape = CapeBase(app=app, db_path="sqlite+aiosqlite:///:memory:", auth_provider=get_user_context)
    cape.permission_required(AuditItem, role="*", actions=["read", "create", "update", "delete"])
    cape.enable_audit_trail(AuditItem)

    async with cape.app.router.lifespan_context(app):
        yield cape

    async with cape.db_session.connect() as conn:
        await conn.run_sync(SQLModel.metadata.drop_all)

@pytest.mark.asyncio
async def test_audit_insert(cape):
    # Create item
    async with cape.get_session(AuthContext(id="test_user")) as session:
        item = AuditItem(name="test item")
        session.add(item)
        await session.commit()

    # # Check audit log
    async with cape.get_privileged_session() as session:
        result = await session.execute(
            text("SELECT * FROM auditlog WHERE table_name = 'audititem'")
        )
        audit = result.first()
        new_values = json.loads(audit.new_values)
        assert audit.action == "INSERT"
        assert audit.user_id == "test_user"
        assert new_values["name"] == "test item"

@pytest.mark.asyncio
async def test_audit_update(cape):
    # Create and update item
    async with cape.get_session(AuthContext(id="test_user")) as session:
        item = AuditItem(name="old name")
        session.add(item)
        await session.commit()
    
        item.name = "new name"
        await session.commit()

    # Check audit log
    async with cape.get_privileged_session() as session:
        result = await session.execute(
            text("SELECT * FROM auditlog WHERE action = 'UPDATE'")
        )
        audit = result.first()
        new_values = json.loads(audit.new_values)
        old_values = json.loads(audit.old_values)
        assert old_values["name"] == "old name"
        assert new_values["name"] == "new name"

@pytest.mark.asyncio
async def test_audit_delete(cape):
    # Create and delete item
    async with cape.get_session(AuthContext(id="test_user")) as session:
        item = AuditItem(name="to delete")
        session.add(item)
        await session.commit()
        
        await session.delete(item)
        await session.commit()

    # Check audit log
    async with cape.get_privileged_session() as session:
        result = await session.execute(
            text("SELECT * FROM auditlog WHERE action = 'DELETE'")
        )
        audit = result.first()
        old_values = json.loads(audit.old_values)
        new_values = json.loads(audit.new_values)
        assert old_values["name"] == "to delete"
        assert new_values is None