# retoor <retoor@molodetz.nl>

import json
import shlex

import pytest

from devplacepy import config
from devplacepy.database import get_table, init_db, set_setting
from devplacepy.services.containers.backend.base import (
    WORKSPACE_MOUNT,
    WORKSPACE_STATE_MOUNT,
)
from devplacepy.services.containers.workspace import editor, quota

OWNER = "editor-owner"
OTHER = "editor-other"


@pytest.fixture(autouse=True)
def _editor_db(tmp_path, monkeypatch):
    init_db()
    monkeypatch.setattr(config, "WORKSPACE_STATE_DIR", tmp_path / "state")
    yield
    get_table(editor.PREFS_TABLE).delete()
    get_table(quota.RULES_TABLE).delete()
    for key, default in editor.DEFAULTS.items():
        setting = editor.SETTING_KEYS[key]
        if isinstance(default, bool):
            set_setting(setting, "1" if default else "0")
        else:
            set_setting(setting, str(default))


def _instance(uid: str = "ws-editor") -> dict:
    return {"uid": uid, "editor_port": editor.EDITOR_DEFAULT_PORT, "is_workspace": 1}


def _prefs(**values) -> dict:
    return editor.save_prefs(OWNER, values)


def test_resolve_uses_site_defaults():
    profile = editor.resolve(OWNER)
    assert profile.theme == editor.DEFAULTS["theme"]
    assert profile.font_size == editor.DEFAULTS["font_size"]
    assert profile.zoom_level == editor.DEFAULTS["zoom_level"]
    assert profile.boot_agent == editor.DEFAULTS["boot_agent"]
    assert profile.trust_all is True


def test_resolve_reads_a_changed_site_setting():
    set_setting("workspace_editor_font_size", "22")
    assert editor.resolve(OWNER).font_size == 22


def test_resolve_prefers_the_user_row():
    _prefs(font_size=20, theme="devplace-light")
    profile = editor.resolve(OWNER)
    assert profile.font_size == 20
    assert profile.theme == "devplace-light"


def test_a_user_row_does_not_leak_to_another_user():
    _prefs(font_size=20)
    assert editor.resolve(OTHER).font_size == editor.DEFAULTS["font_size"]


def test_zero_and_empty_inherit_but_zoom_zero_does_not():
    set_setting("workspace_editor_font_size", "22")
    set_setting("workspace_editor_zoom_level", "3")
    _prefs(font_size=0, theme="", zoom_level=0)
    profile = editor.resolve(OWNER)
    assert profile.font_size == 22
    assert profile.theme == editor.DEFAULTS["theme"]
    assert profile.zoom_level == 0


def test_the_zoom_sentinel_inherits():
    set_setting("workspace_editor_zoom_level", "3")
    _prefs(zoom_level=editor.INHERIT_ZOOM)
    assert editor.resolve(OWNER).zoom_level == 3


def test_the_boot_shell_sentinel_inherits_but_zero_does_not():
    _prefs(boot_shell=editor.INHERIT_FLAG)
    assert editor.resolve(OWNER).boot_shell is True
    _prefs(boot_shell=0)
    assert editor.resolve(OWNER).boot_shell is False


def test_out_of_range_stored_values_are_clamped():
    _prefs(font_size=999, zoom_level=99, window_width=1)
    profile = editor.resolve(OWNER)
    assert profile.font_size == editor.BOUNDS["font_size"][1]
    assert profile.zoom_level == editor.BOUNDS["zoom_level"][1]
    assert profile.window_width == editor.BOUNDS["window_width"][0]


def test_an_unknown_choice_falls_back_to_the_default():
    _prefs(theme="hot-pink", layout="chaos", window_mode="teleport")
    profile = editor.resolve(OWNER)
    assert profile.theme == editor.DEFAULTS["theme"]
    assert profile.layout == editor.DEFAULTS["layout"]
    assert profile.window_mode == editor.DEFAULTS["window_mode"]


def test_the_container_size_comes_from_the_quota_resolver():
    profile = editor.resolve(OWNER, {"workspace_cpu_millicores": 3000})
    assert profile.cpu_millicores == 3000
    assert profile.cpu_cores() == 3.0
    assert profile.cpu_limit() == "3"
    assert profile.mem_limit() == quota.format_memory(profile.memory_mb)


def test_source_map_reports_where_each_value_came_from():
    _prefs(font_size=20)
    sources = editor.source_map(OWNER)
    assert sources["font_size"] == editor.SOURCE_USER
    assert sources["theme"] == editor.SOURCE_SITE
    assert sources["trust_all"] == editor.SOURCE_SITE


def test_reset_removes_the_row_and_restores_the_defaults():
    _prefs(font_size=20)
    assert editor.reset_prefs(OWNER, OWNER) is True
    assert editor.resolve(OWNER).font_size == editor.DEFAULTS["font_size"]
    assert editor.source_map(OWNER)["font_size"] == editor.SOURCE_SITE


def test_reset_on_a_user_with_no_row_is_a_no_op():
    assert editor.reset_prefs(OWNER, OWNER) is False


def test_saving_twice_updates_one_row():
    _prefs(font_size=20)
    _prefs(font_size=21)
    rows = list(get_table(editor.PREFS_TABLE).find(owner_id=OWNER, deleted_at=None))
    assert len(rows) == 1
    assert rows[0]["font_size"] == 21


def test_merge_managed_seeds_an_absent_key():
    merged, managed = editor.merge_managed({}, {}, {"editor.fontSize": 14})
    assert merged["editor.fontSize"] == 14
    assert managed == {"editor.fontSize": 14}


def test_merge_managed_updates_a_value_we_still_own():
    merged, _ = editor.merge_managed(
        {"editor.fontSize": 14}, {"editor.fontSize": 14}, {"editor.fontSize": 18}
    )
    assert merged["editor.fontSize"] == 18


def test_merge_managed_respects_a_member_edit_forever():
    current = {"editor.fontSize": 30}
    managed = {"editor.fontSize": 14}
    for desired in (16, 18, 20):
        current, managed = editor.merge_managed(
            current, managed, {"editor.fontSize": desired}
        )
        assert current["editor.fontSize"] == 30


def test_merge_managed_keeps_unrelated_member_keys():
    merged, _ = editor.merge_managed(
        {"files.autoSave": "on"}, {}, {"editor.fontSize": 14}
    )
    assert merged["files.autoSave"] == "on"


def test_merge_managed_is_idempotent():
    desired = editor.settings_for(editor.resolve(OWNER))
    first, managed = editor.merge_managed({}, {}, desired)
    second, _ = editor.merge_managed(first, managed, desired)
    assert first == second


def test_settings_disable_workspace_trust_when_trust_all():
    settings = editor.settings_for(editor.resolve(OWNER))
    assert settings["security.workspace.trust.enabled"] is False
    assert settings["security.workspace.trust.startupPrompt"] == "never"
    assert settings["task.allowAutomaticTasks"] == "on"


def test_settings_leave_trust_alone_when_the_kill_switch_is_off():
    set_setting("workspace_editor_trust_all", "0")
    settings = editor.settings_for(editor.resolve(OWNER))
    assert "security.workspace.trust.enabled" not in settings


def test_settings_carry_the_devplace_terminal_profile():
    settings = editor.settings_for(editor.resolve(OWNER))
    profiles = settings["terminal.integrated.profiles.linux"]
    assert profiles["DevPlace Code"]["path"] == "/usr/bin/dpc"
    assert settings["terminal.integrated.defaultProfile.linux"] == "bash"


def test_settings_apply_the_layout_preset():
    _prefs(layout="zen")
    settings = editor.settings_for(editor.resolve(OWNER))
    assert settings["workbench.activityBar.location"] == "hidden"
    assert settings["editor.minimap.enabled"] is False


def test_the_system_theme_is_left_to_the_member():
    _prefs(theme="system")
    assert "workbench.colorTheme" not in editor.settings_for(editor.resolve(OWNER))


def test_seed_state_writes_the_whole_tree():
    instance = _instance()
    profile = editor.resolve(OWNER)
    assert editor.seed_state(instance, profile) is True
    root = editor.state_dir(instance)
    settings = json.loads((root / "data" / "User" / "settings.json").read_text())
    managed = json.loads((root / "data" / "User" / editor.MANAGED_FILE).read_text())
    payload = json.loads((root / editor.PROFILE_FILE).read_text())
    assert settings["editor.fontSize"] == profile.font_size
    assert managed["editor.fontSize"] == profile.font_size
    assert payload["editor"]["theme"] == profile.theme
    assert payload["app_name"] == editor.APP_NAME


def test_seed_state_never_clobbers_a_member_edit():
    instance = _instance()
    editor.seed_state(instance, editor.resolve(OWNER))
    settings_path = editor.state_dir(instance) / "data" / "User" / "settings.json"
    stored = json.loads(settings_path.read_text())
    stored["editor.fontSize"] = 30
    stored["files.autoSave"] = "on"
    settings_path.write_text(json.dumps(stored))

    set_setting("workspace_editor_font_size", "18")
    set_setting("workspace_editor_terminal_font_size", "20")
    editor.seed_state(instance, editor.resolve(OWNER))

    written = json.loads(settings_path.read_text())
    assert written["editor.fontSize"] == 30
    assert written["files.autoSave"] == "on"
    assert written["terminal.integrated.fontSize"] == 20


def test_seed_state_replaces_an_unparseable_settings_file():
    instance = _instance()
    user_dir = editor.state_dir(instance) / "data" / "User"
    user_dir.mkdir(parents=True, exist_ok=True)
    (user_dir / "settings.json").write_text("{not json at all")
    assert editor.seed_state(instance, editor.resolve(OWNER)) is True
    assert json.loads((user_dir / "settings.json").read_text())["editor.fontSize"]


def test_seed_state_fails_soft_on_an_unwritable_directory(monkeypatch):
    def explode(*args, **kwargs):
        raise OSError("read-only file system")

    monkeypatch.setattr("pathlib.Path.mkdir", explode)
    assert editor.seed_state(_instance(), editor.resolve(OWNER)) is False


def test_argv_carries_the_devplace_brand():
    instance = _instance()
    command = editor.argv(instance, editor.resolve(OWNER))
    assert command[0] == "code-server"
    assert command[command.index("--app-name") + 1] == editor.APP_NAME
    assert "--disable-getting-started-override" in command
    assert "--disable-telemetry" in command


def test_argv_disables_workspace_trust_when_trust_all():
    command = editor.argv(_instance(), editor.resolve(OWNER))
    assert "--disable-workspace-trust" in command


def test_argv_keeps_workspace_trust_when_the_kill_switch_is_off():
    set_setting("workspace_editor_trust_all", "0")
    command = editor.argv(_instance(), editor.resolve(OWNER))
    assert "--disable-workspace-trust" not in command


def test_argv_keeps_password_auth():
    command = editor.argv(_instance(), editor.resolve(OWNER))
    assert command[command.index("--auth") + 1] == "password"
    assert "none" not in command


def test_argv_paths_sit_under_the_state_mount():
    command = editor.argv(_instance(), editor.resolve(OWNER))
    assert command[command.index("--user-data-dir") + 1].startswith(
        WORKSPACE_STATE_MOUNT
    )
    assert command[command.index("--extensions-dir") + 1].startswith(
        WORKSPACE_STATE_MOUNT
    )
    assert command[-1] == WORKSPACE_MOUNT


def test_argv_binds_the_instance_editor_port():
    instance = _instance()
    instance["editor_port"] = 9443
    command = editor.argv(instance, editor.resolve(OWNER))
    assert command[command.index("--bind-addr") + 1] == "0.0.0.0:9443"


def test_every_optional_flag_is_declared():
    command = editor.argv(_instance(), editor.resolve(OWNER))
    for flag in editor.OPTIONAL_FLAGS:
        assert flag in command


def test_wrap_with_env_export_runs_the_original_command_through_a_shell():
    command = editor.argv(_instance(), editor.resolve(OWNER))
    wrapped = editor.wrap_with_env_export(command)
    assert wrapped[0] == "/bin/sh"
    assert wrapped[1] == "-c"
    script = wrapped[2]
    assert script.rstrip().endswith(shlex.join(command))


def test_wrap_with_env_export_writes_only_devplace_prefixed_vars():
    script = editor.wrap_with_env_export(["true"])[2]
    assert "python3 -c" in script
    assert editor.ENV_EXPORT_FILE in script
    assert "DEVPLACE_" in script
    assert "startswith" in script


def test_env_for_exports_the_profile_to_the_container():
    env = editor.env_for(editor.resolve(OWNER))
    assert env["DEVPLACE_EDITOR_APP_NAME"] == editor.APP_NAME
    assert env["DEVPLACE_EDITOR_PROFILE"].endswith(editor.PROFILE_FILE)
    assert env["DEVPLACE_EDITOR_BOOT_AGENT"] == editor.DEFAULTS["boot_agent"]
    assert env["DEVPLACE_EDITOR_TRUST_ALL"] == "1"
    assert all(isinstance(value, str) for value in env.values())


def test_restart_is_not_required_before_the_first_boot():
    instance = _instance()
    assert editor.restart_required(instance, editor.resolve(OWNER)) is False


def test_restart_is_not_required_when_nothing_changed():
    instance = _instance()
    profile = editor.resolve(OWNER)
    editor.seed_state(instance, profile)
    assert editor.restart_required(instance, profile) is False


def test_restart_is_required_after_a_preference_change():
    instance = _instance()
    editor.seed_state(instance, editor.resolve(OWNER))
    _prefs(font_size=27)
    assert editor.restart_required(instance, editor.resolve(OWNER)) is True


def test_view_carries_the_profile_and_its_sources():
    _prefs(theme="devplace-light")
    payload = editor.view(OWNER)
    assert payload["theme"] == "devplace-light"
    assert payload["sources"]["theme"] == editor.SOURCE_USER
    assert payload["cpu_cores"] == editor.resolve(OWNER).cpu_cores()


def test_settings_suppress_a_foreign_ai_assistant():
    settings = editor.settings_for(editor.resolve(OWNER))
    assert settings["chat.disableAIFeatures"] is True
    assert settings["workbench.secondarySideBar.defaultVisibility"] == "hidden"


def test_settings_open_straight_into_the_workspace():
    assert editor.settings_for(editor.resolve(OWNER))["workbench.startupEditor"] == "none"


def test_the_agent_working_files_never_sync_into_the_project():
    from devplacepy.project_files import SYNC_SKIP_NAMES

    assert ".dpc" in SYNC_SKIP_NAMES
    assert "dpc.log" in SYNC_SKIP_NAMES
    assert ".devplace" in SYNC_SKIP_NAMES
