import json

import pytest

from app.core.data_source.credentials import CredentialCodec
from app.core.data_source.models import DataSourceCredential


UID = "01900000-0000-7000-8000-000000000010"


class FakeResult:
    def __init__(self, records=None):
        self.records = records or []

    def __iter__(self):
        return iter(self.records)

    def single(self):
        return self.records[0] if self.records else None


class FakeGraph:
    def __init__(self, node):
        self.node = dict(node)
        self.calls = []

    def run(self, query, parameters=None, **kwargs):
        values = parameters or kwargs
        self.calls.append((query, values))
        if "RETURN elementId(n) AS legacy_id" in query:
            return FakeResult(
                [
                    {
                        "legacy_id": "legacy-7",
                        "properties": dict(self.node),
                    }
                ]
            )
        if "RETURN properties(n) AS properties" in query:
            return FakeResult([{"properties": dict(self.node)}])
        if "SET n.uid = $uid" in query:
            self.node["uid"] = values["uid"]
        if "SET n.credential_ref" in query:
            self.node["credential_ref"] = values["credential_ref"]
            self.node["credential_version"] = values[
                "credential_version"
            ]
        if "REMOVE n.username" in query:
            self.node.pop("username", None)
            self.node.pop("password", None)
            self.node.pop("conn_str", None)
        return FakeResult()


class FakePlatformSession:
    def __init__(self):
        self.commits = 0
        self.rollbacks = 0

    def commit(self):
        self.commits += 1

    def rollback(self):
        self.rollbacks += 1


class FakeCredentialRepository:
    def __init__(self, mismatch=False):
        self.codec = CredentialCodec(b"k" * 32, "v1")
        self.mismatch = mismatch
        self.created = []
        self.compensated = []

    def create_version(
        self,
        session,
        *,
        data_source_uid,
        credential,
        actor_uid,
    ):
        del session, actor_uid
        self.created.append(credential)
        sealed = self.codec.encrypt(data_source_uid, 1, credential)
        if self.mismatch:
            return self.codec.encrypt(
                data_source_uid,
                1,
                DataSourceCredential("other", "wrong"),
            )
        return sealed

    def compensate_failed_activation(self, session, **kwargs):
        del session
        self.compensated.append(kwargs)


def test_report_never_returns_plaintext():
    from app.commands.report_datasource_credentials import (
        inspect_legacy_datasources,
    )

    graph = FakeGraph(
        {
            "uid": UID,
            "type": "postgresql",
            "host": "private-db",
            "port": 5432,
            "database": "analytics",
            "username": "legacy-reader",
            "password": "legacy-password",
        }
    )

    report = inspect_legacy_datasources(graph)

    assert report == [
        {
            "data_source_uid": UID,
            "issues": ["plaintext_credentials"],
        }
    ]
    output = json.dumps(report)
    assert "legacy-password" not in output
    assert "legacy-reader" not in output
    assert "private-db" not in output


def test_migration_removes_plaintext_only_after_round_trip_verification():
    from app.commands.migrate_datasource_credentials import migrate_one

    graph = FakeGraph(
        {
            "uid": UID,
            "type": "postgresql",
            "host": "db",
            "port": 5432,
            "database": "analytics",
            "username": "legacy-reader",
            "password": "legacy-password",
        }
    )
    credentials = FakeCredentialRepository()
    session = FakePlatformSession()

    result = migrate_one(
        UID,
        graph,
        credentials,
        session,
        actor_uid=None,
    )

    assert result.status == "migrated"
    assert credentials.created[0].password == "legacy-password"
    assert "password" not in graph.node
    assert "username" not in graph.node
    assert graph.node["credential_version"] == 1
    assert session.commits == 1


def test_failed_round_trip_keeps_legacy_plaintext_for_recovery():
    from app.commands.migrate_datasource_credentials import migrate_one

    graph = FakeGraph(
        {
            "uid": UID,
            "type": "postgresql",
            "host": "db",
            "port": 5432,
            "database": "analytics",
            "username": "legacy-reader",
            "password": "legacy-password",
        }
    )
    credentials = FakeCredentialRepository(mismatch=True)
    session = FakePlatformSession()

    with pytest.raises(ValueError, match="round-trip"):
        migrate_one(
            UID,
            graph,
            credentials,
            session,
            actor_uid=None,
        )

    assert graph.node["password"] == "legacy-password"
    assert "credential_ref" not in graph.node


def test_migration_cli_requires_explicit_confirmation_flag():
    source = open(
        "app/commands/migrate_datasource_credentials.py",
        encoding="utf-8",
    ).read()

    assert "--confirm-encrypt-and-remove-plaintext" in source
    assert "if not args.confirm_encrypt_and_remove_plaintext" in source
