import base64
import json
import sqlite3

import pytest

from src.today_store import TodayStore
from src.state_encryption import (
    PrivateStateCipher,
    PrivateStateEncryptionError,
    decode_private_state_encryption_keyring,
    private_state_encryption_config,
)


def encoded(byte: bytes) -> str:
    return base64.b64encode(byte * 32).decode()


def test_keyring_writes_active_versioned_envelope_and_reads_it_current():
    keys, active = decode_private_state_encryption_keyring(
        json.dumps({"old": encoded(b"o"), "next": encoded(b"n")}), "next"
    )

    cipher = PrivateStateCipher(keys, active_key_id=active, store="today")
    payload = cipher.seal({"ids": [7]}, binding="timmy")

    assert payload.startswith("v2:next:")
    assert cipher.open(payload, binding="timmy") == ({"ids": [7]}, False)


def test_keyring_reads_legacy_v1_and_marks_it_for_rewrap():
    legacy = PrivateStateCipher(b"o" * 32, store="today")
    payload = legacy.seal({"ids": [7]}, binding="timmy")
    rotating = PrivateStateCipher(
        {"old": b"o" * 32, "next": b"n" * 32},
        active_key_id="next",
        store="today",
    )

    assert rotating.open(payload, binding="timmy") == ({"ids": [7]}, True)


def test_keyring_reads_inactive_v2_and_marks_it_for_rewrap():
    old = PrivateStateCipher(
        {"old": b"o" * 32, "next": b"n" * 32},
        active_key_id="old",
        store="today",
    )
    payload = old.seal({"ids": [7]}, binding="timmy")
    rotating = PrivateStateCipher(
        {"old": b"o" * 32, "next": b"n" * 32},
        active_key_id="next",
        store="today",
    )

    assert rotating.open(payload, binding="timmy") == ({"ids": [7]}, True)


def test_keyring_fails_closed_for_unknown_key_id():
    cipher = PrivateStateCipher(
        {"next": b"n" * 32}, active_key_id="next", store="today"
    )

    with pytest.raises(PrivateStateEncryptionError, match="could not be decrypted"):
        cipher.open("v2:retired:AAAA", binding="timmy")


def test_keyring_configuration_is_bounded_and_requires_active_key():
    with pytest.raises(PrivateStateEncryptionError, match="active key"):
        decode_private_state_encryption_keyring(
            json.dumps({"old": encoded(b"o")}), "missing"
        )
    with pytest.raises(PrivateStateEncryptionError, match="at most 4"):
        decode_private_state_encryption_keyring(
            json.dumps({str(i): encoded(bytes([65 + i])) for i in range(5)}), "0"
        )


def test_environment_prefers_keyring_and_keeps_single_key_rollout(monkeypatch):
    monkeypatch.setenv("STACKCHAIN_PRIVATE_STATE_ENCRYPTION_KEY", encoded(b"l"))
    monkeypatch.delenv("STACKCHAIN_PRIVATE_STATE_ENCRYPTION_KEYS", raising=False)
    monkeypatch.delenv("STACKCHAIN_PRIVATE_STATE_ACTIVE_KEY_ID", raising=False)
    assert private_state_encryption_config() == b"l" * 32

    monkeypatch.setenv(
        "STACKCHAIN_PRIVATE_STATE_ENCRYPTION_KEYS",
        json.dumps({"legacy": encoded(b"l"), "next": encoded(b"n")}),
    )
    monkeypatch.setenv("STACKCHAIN_PRIVATE_STATE_ACTIVE_KEY_ID", "next")
    keys, active = private_state_encryption_config()
    assert keys == {"legacy": b"l" * 32, "next": b"n" * 32}
    assert active == "next"


def test_today_store_reads_and_lazily_rewraps_v2_state_during_rotation(tmp_path, monkeypatch):
    path = tmp_path / "today.sqlite3"
    legacy = TodayStore(path, encryption_key=b"l" * 32)
    legacy.apply("timmy", "seed", "add", "issue:stackchain/dashboard#1237")

    monkeypatch.setenv(
        "STACKCHAIN_PRIVATE_STATE_ENCRYPTION_KEYS",
        json.dumps({"legacy": encoded(b"l"), "next": encoded(b"n")}),
    )
    monkeypatch.setenv("STACKCHAIN_PRIVATE_STATE_ACTIVE_KEY_ID", "next")
    rotating = TodayStore(path)

    assert rotating.get("timmy")["ids"] == ["issue:stackchain/dashboard#1237"]
    assert rotating.apply(
        "timmy", "add-next", "add", "issue:stackchain/dashboard#1238"
    )["ids"] == [
        "issue:stackchain/dashboard#1237",
        "issue:stackchain/dashboard#1238",
    ]
    with sqlite3.connect(path) as connection:
        payload = connection.execute(
            "SELECT ids FROM today_plans WHERE login = 'timmy'"
        ).fetchone()[0]
    assert payload.startswith("v2:next:")
