import asyncio
from types import SimpleNamespace
from unittest.mock import AsyncMock
from uuid import uuid4

import pytest
from cryptography.fernet import InvalidToken

from app.infra.repositories.metadata_repository import MetadataRepository


class _DummyResult:
    def __init__(self, record):
        self._record = record

    def scalar_one_or_none(self):
        return self._record


class _DummyEncryptor:
    def __init__(self, decrypted=None, raise_invalid_token=False):
        self._decrypted = decrypted
        self._raise_invalid_token = raise_invalid_token
        self.encrypted_values = []

    def decrypt(self, _value):
        if self._raise_invalid_token:
            raise InvalidToken()
        return self._decrypted

def _build_record(dsn_encrypted: str):
    return SimpleNamespace(
        project_id=uuid4(),
        db_role="biz_data",
        db_type="postgresql",
        dsn_encrypted=dsn_encrypted,
        pool_min_size=1,
        pool_max_size=5,
    )


def test_invalid_token_with_plaintext_dsn_value_raises_clear_error(monkeypatch):
    record = _build_record("postgresql://user:p@ss@localhost:5432/db")
    session = SimpleNamespace(
        execute=None,
        commit=None,
    )
    session.execute = AsyncMock(return_value=_DummyResult(record))
    session.commit = AsyncMock()
    encryptor = _DummyEncryptor(raise_invalid_token=True)
    repo = MetadataRepository(session)

    monkeypatch.setattr(
        "app.infra.repositories.metadata_repository.is_database_encryption_configured",
        lambda: True,
    )
    monkeypatch.setattr(
        "app.infra.repositories.metadata_repository.get_database_encryptor",
        lambda: encryptor,
    )

    with pytest.raises(
        ValueError,
        match="DATABASE_ENCRYPTION_KEY mismatch or invalid dsn_encrypted value",
    ):
        asyncio.run(repo.get_project_db_routing(record.project_id, "biz_data"))
    session.commit.assert_not_awaited()


def test_invalid_token_with_non_dsn_value_raises_clear_error(monkeypatch):
    record = _build_record("gAAAAABinvalidciphertext")
    session = SimpleNamespace(
        execute=None,
        commit=None,
    )
    session.execute = AsyncMock(return_value=_DummyResult(record))
    session.commit = AsyncMock()
    repo = MetadataRepository(session)

    monkeypatch.setattr(
        "app.infra.repositories.metadata_repository.is_database_encryption_configured",
        lambda: True,
    )
    monkeypatch.setattr(
        "app.infra.repositories.metadata_repository.get_database_encryptor",
        lambda: _DummyEncryptor(raise_invalid_token=True),
    )

    with pytest.raises(
        ValueError,
        match="DATABASE_ENCRYPTION_KEY mismatch or invalid dsn_encrypted value",
    ):
        asyncio.run(repo.get_project_db_routing(record.project_id, "biz_data"))
    session.commit.assert_not_awaited()


def test_encrypted_dsn_decrypts_without_migration(monkeypatch):
    record = _build_record("encrypted-value")
    session = SimpleNamespace(
        execute=None,
        commit=None,
    )
    session.execute = AsyncMock(return_value=_DummyResult(record))
    session.commit = AsyncMock()
    repo = MetadataRepository(session)

    monkeypatch.setattr(
        "app.infra.repositories.metadata_repository.is_database_encryption_configured",
        lambda: True,
    )
    monkeypatch.setattr(
        "app.infra.repositories.metadata_repository.get_database_encryptor",
        lambda: _DummyEncryptor(decrypted="postgresql://u:p@ss@host/db"),
    )

    routing = asyncio.run(repo.get_project_db_routing(record.project_id, "biz_data"))

    assert routing.dsn == "postgresql://u:p%40ss@host/db"
    session.commit.assert_not_awaited()
