# SPDX-License-Identifier: AGPL-3.0-only
# Copyright (c) 2026 sol pbc

from __future__ import annotations

import json
from importlib import import_module

import pytest
from flask import Blueprint, Flask

import solstone.convey.state as convey_state
import solstone.think.utils as think_utils

journal_sources = import_module("solstone.apps.import.journal_sources")
ingest = import_module("solstone.apps.import.ingest")

create_state_directory = journal_sources.create_state_directory
generate_key = journal_sources.generate_key
get_state_directory = journal_sources.get_state_directory
load_journal_source = journal_sources.load_journal_source
save_journal_source = journal_sources.save_journal_source
register_ingest_routes = ingest.register_ingest_routes


@pytest.fixture
def journal_env(tmp_path, monkeypatch):
    monkeypatch.setattr(convey_state, "journal_root", str(tmp_path), raising=False)
    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    think_utils._journal_path_cache = None
    (tmp_path / "apps" / "import" / "journal_sources").mkdir(
        parents=True, exist_ok=True
    )
    return tmp_path


def _source(name="test-source", key=None, **overrides):
    if key is None:
        key = generate_key()
    source = {
        "name": name,
        "key": key,
        "created_at": 1000,
        "enabled": True,
        "revoked": False,
        "revoked_at": None,
        "stats": {
            "segments_received": 0,
            "entities_received": 0,
            "facets_received": 0,
            "imports_received": 0,
            "config_received": 0,
        },
    }
    source.update(overrides)
    return source


@pytest.fixture
def ingest_env(journal_env):
    key = generate_key()
    source = _source(key=key)
    save_journal_source(source)
    key_prefix = key[:8]
    create_state_directory(journal_env, key_prefix)

    app = Flask(__name__)
    app.config["TESTING"] = True
    bp = Blueprint("import-test", __name__, url_prefix="/app/import")
    register_ingest_routes(bp)
    app.register_blueprint(bp)

    return {
        "root": journal_env,
        "key": key,
        "key_prefix": key_prefix,
        "source": source,
        "client": app.test_client(),
    }


def _post_config(client, key, key_prefix, config):
    return client.post(
        f"/app/import/journal/{key_prefix}/ingest/config",
        headers={"Authorization": f"Bearer {key}"},
        json={"config": config},
    )


def _sample_config():
    return {
        "identity": {"name": "Remote User", "preferred": "Remote", "timezone": "UTC"},
        "convey": {
            "allow_network_access": False,
            "password_hash": "secret_hash",
            "secret": "secret_value",
        },
        "setup": {"completed_at": 12345},
        "providers": {"auth": "provider_auth_val", "key_validation": "val123"},
        "transcribe": {"parakeet-cpp": {"device": "cpu"}},
        "env": {"API_KEY": "xyz"},
        "retention": {"days": 30},
    }


def _write_target_config(root, config):
    config_dir = root / "config"
    config_dir.mkdir(parents=True, exist_ok=True)
    (config_dir / "journal.json").write_text(
        json.dumps(config, ensure_ascii=False), encoding="utf-8"
    )
    think_utils._journal_path_cache = None


def _read_json(path):
    return json.loads(path.read_text(encoding="utf-8"))


def _read_log(key_prefix):
    log_path = get_state_directory(key_prefix) / "config" / "log.jsonl"
    if not log_path.exists():
        return []
    return [
        json.loads(line)
        for line in log_path.read_text(encoding="utf-8").splitlines()
        if line.strip()
    ]


def test_auth_required(ingest_env):
    env = ingest_env
    response = env["client"].post(
        f"/app/import/journal/{env['key_prefix']}/ingest/config",
        json={"config": {}},
    )
    assert response.status_code == 401


def test_key_prefix_mismatch(ingest_env):
    env = ingest_env
    response = env["client"].post(
        "/app/import/journal/deadbeef/ingest/config",
        headers={"Authorization": f"Bearer {env['key']}"},
        json={"config": {}},
    )
    assert response.status_code == 403


def test_invalid_json(ingest_env):
    env = ingest_env
    response = env["client"].post(
        f"/app/import/journal/{env['key_prefix']}/ingest/config",
        headers={"Authorization": f"Bearer {env['key']}"},
        data="not-json",
        content_type="application/json",
    )
    assert response.status_code == 400
    payload = response.get_json()
    assert payload["error"] == "I couldn't read that JSON request."
    assert payload["reason_code"] == "invalid_json_request"
    assert payload["detail"] == "Invalid JSON body"


def test_missing_config(ingest_env):
    env = ingest_env
    response = env["client"].post(
        f"/app/import/journal/{env['key_prefix']}/ingest/config",
        headers={"Authorization": f"Bearer {env['key']}"},
        json={},
    )
    assert response.status_code == 400
    payload = response.get_json()
    assert payload["error"] == "I couldn't find a required field."
    assert payload["reason_code"] == "missing_required_field"
    assert payload["detail"] == "Missing config object"


def test_config_staged(ingest_env):
    env = ingest_env
    target_config = {"identity": {"name": "Local User"}, "retention": {"days": 90}}
    _write_target_config(env["root"], target_config)
    config = _sample_config()

    response = _post_config(env["client"], env["key"], env["key_prefix"], config)
    body = response.get_json()
    state_dir = get_state_directory(env["key_prefix"]) / "config"
    source = load_journal_source(env["key"])

    assert response.status_code == 200
    assert body == {"staged": True, "skipped": False, "diff_fields": 11}
    assert (state_dir / "source_config.json").exists()
    assert (state_dir / "diff.json").exists()
    assert "last_hash" in _read_json(state_dir / "state.json")
    assert _read_log(env["key_prefix"])[0]["action"] == "staged"
    assert source["stats"]["config_received"] == 1


def test_diff_categorization(ingest_env):
    env = ingest_env
    target_config = {"identity": {"name": "Local User"}, "retention": {"days": 90}}
    _write_target_config(env["root"], target_config)
    config = {"identity": {"name": "Remote User"}, "retention": {"days": 30}}

    response = _post_config(env["client"], env["key"], env["key_prefix"], config)
    diff = _read_json(get_state_directory(env["key_prefix"]) / "config" / "diff.json")

    assert response.status_code == 200
    assert diff["identity.name"]["category"] == "transferable"
    assert diff["retention.days"]["category"] == "preference"


def test_never_transfer_excluded(ingest_env):
    env = ingest_env
    _write_target_config(env["root"], {"identity": {"name": "Local User"}})
    config = _sample_config()

    response = _post_config(env["client"], env["key"], env["key_prefix"], config)
    diff = _read_json(get_state_directory(env["key_prefix"]) / "config" / "diff.json")

    assert response.status_code == 200
    assert "convey.password_hash" not in diff
    assert "convey.secret" in diff
    assert diff["convey.secret"]["category"] == "preference"
    assert "setup.completed_at" in diff
    assert "providers.auth" in diff
    assert "providers.key_validation" in diff
    assert "transcribe.parakeet-cpp.device" in diff
    assert any(key.startswith("env.") for key in diff)


def test_idempotent(ingest_env):
    env = ingest_env
    _write_target_config(env["root"], {"identity": {"name": "Local User"}})
    config = _sample_config()

    first = _post_config(env["client"], env["key"], env["key_prefix"], config)
    second = _post_config(env["client"], env["key"], env["key_prefix"], config)

    assert first.status_code == 200
    assert second.status_code == 200
    assert second.get_json() == {
        "staged": False,
        "skipped": True,
        "reason": "idempotent",
    }


def test_config_always_staged(ingest_env):
    env = ingest_env
    _write_target_config(env["root"], {"identity": {"name": "Local User"}})
    config = {"identity": {"name": "Remote User"}}

    response = _post_config(env["client"], env["key"], env["key_prefix"], config)

    assert response.status_code == 200
    assert response.get_json()["staged"] is True
