import base64
import dataclasses

import pytest


DATA_SOURCE_A = "01900000-0000-7000-8000-000000000010"
DATA_SOURCE_B = "01900000-0000-7000-8000-000000000011"


def test_credential_round_trip_and_tamper_detection():
    from app.core.data_source.credentials import CredentialCodec
    from app.core.data_source.errors import DataSourceCredentialUnavailable
    from app.core.data_source.models import DataSourceCredential

    codec = CredentialCodec(master_key=b"k" * 32, key_version="v1")
    credential = DataSourceCredential(
        username="reader",
        password="secret",
        options={"auth_database": "source_db"},
    )

    sealed = codec.encrypt(DATA_SOURCE_A, 1, credential)

    assert b"secret" not in sealed.encrypted_payload
    assert "secret" not in repr(credential)
    assert codec.decrypt(sealed) == credential

    tampered = dataclasses.replace(
        sealed,
        encrypted_payload=sealed.encrypted_payload[:-1] + b"x",
    )
    with pytest.raises(
        DataSourceCredentialUnavailable,
        match="credential cannot be decrypted",
    ):
        codec.decrypt(tampered)


def test_credential_cannot_be_replayed_for_another_datasource():
    from app.core.data_source.credentials import CredentialCodec
    from app.core.data_source.errors import DataSourceCredentialUnavailable
    from app.core.data_source.models import DataSourceCredential

    codec = CredentialCodec(master_key=b"k" * 32, key_version="v1")
    credential = DataSourceCredential("reader", "secret")
    sealed = codec.encrypt(DATA_SOURCE_A, 1, credential)
    replayed = dataclasses.replace(sealed, data_source_uid=DATA_SOURCE_B)

    with pytest.raises(DataSourceCredentialUnavailable):
        codec.decrypt(replayed)


def test_codec_requires_a_32_byte_base64_master_key():
    from app.core.data_source.credentials import CredentialCodec
    from app.core.data_source.errors import DataSourceCredentialUnavailable

    encoded = base64.urlsafe_b64encode(b"k" * 32).decode("ascii")

    assert CredentialCodec.from_base64(encoded, "v1").key_version == "v1"

    with pytest.raises(
        DataSourceCredentialUnavailable,
        match="master key is not configured",
    ):
        CredentialCodec.from_base64("", "v1")

    with pytest.raises(
        DataSourceCredentialUnavailable,
        match="master key must decode to 32 bytes",
    ):
        CredentialCodec.from_base64(
            base64.urlsafe_b64encode(b"short").decode("ascii"),
            "v1",
        )


class _ScalarResult:
    def __init__(self, value):
        self.value = value

    def scalar_one(self):
        return self.value


class RecordingSession:
    def __init__(self):
        self.statements = []

    def execute(self, statement, parameters=None):
        sql = str(statement)
        self.statements.append((sql, parameters or {}))
        if "COALESCE(MAX(credential_version)" in sql:
            return _ScalarResult(3)
        return _ScalarResult(None)


def test_repository_versions_credentials_under_an_advisory_lock():
    from app.core.data_source.credentials import (
        CredentialCodec,
        DataSourceCredentialRepository,
    )
    from app.core.data_source.models import DataSourceCredential

    codec = CredentialCodec(master_key=b"k" * 32, key_version="v1")
    repository = DataSourceCredentialRepository(codec)
    session = RecordingSession()

    sealed = repository.create_version(
        session,
        data_source_uid=DATA_SOURCE_A,
        credential=DataSourceCredential("reader", "secret"),
        actor_uid=None,
    )

    assert sealed.credential_version == 3
    sql = "\n".join(statement for statement, _ in session.statements)
    assert "pg_advisory_xact_lock" in sql
    assert "status = 'retired'" in sql
    assert "INSERT INTO public.datasource_credentials" in sql
    assert "INSERT INTO public.datasource_credential_audit_events" in sql
    assert "secret" not in sql
    assert all(
        "secret" not in repr(parameters)
        for _, parameters in session.statements
    )


def test_repository_compensation_restores_prior_active_version():
    from app.core.data_source.credentials import (
        CredentialCodec,
        DataSourceCredentialRepository,
    )

    repository = DataSourceCredentialRepository(
        CredentialCodec(master_key=b"k" * 32, key_version="v1")
    )
    session = RecordingSession()

    repository.compensate_failed_activation(
        session,
        data_source_uid=DATA_SOURCE_A,
        failed_version=3,
        restore_version=2,
        actor_uid=None,
    )

    sql = "\n".join(statement for statement, _ in session.statements)
    assert "status = 'revoked'" in sql
    assert "status = 'active'" in sql
    assert "credential_activation_compensated" in repr(session.statements)
