"""Regression coverage for Kanban board default workdir plumbing."""

import importlib
import json
import re
import subprocess
from pathlib import Path

from tests.js_source_extract import extract_function


ROOT = Path(__file__).resolve().parents[1]
INDEX = (ROOT / "static" / "index.html").read_text(encoding="utf-8")
PANELS = (ROOT / "static" / "panels.js").read_text(encoding="utf-8")
I18N = (ROOT / "static" / "i18n.js").read_text(encoding="utf-8")


class _Boards:
    def __init__(self):
        self.boards = {"default": {"slug": "default", "name": "Default", "default_workdir": "/old"}}

    def _normalize_board_slug(self, slug):
        return str(slug).strip()

    def board_exists(self, slug):
        return slug in self.boards

    def create_board(self, slug, **kwargs):
        self.boards.setdefault(slug, {"slug": slug, "name": slug, "archived": False})
        self.boards[slug].update({key: value for key, value in kwargs.items() if value is not None})
        return dict(self.boards[slug])

    def write_board_metadata(self, slug, **kwargs):
        self.boards[slug].update({key: value for key, value in kwargs.items() if value is not None})
        return dict(self.boards[slug])

    def get_current_board(self):
        return "default"

    def set_current_board(self, slug):
        return None


def _bridge(monkeypatch):
    bridge = importlib.import_module("api.kanban_bridge")
    boards = _Boards()
    monkeypatch.setattr(bridge, "_kb", lambda: boards)
    return bridge, boards


def test_board_payload_validates_default_workdir_and_preserves_omission(monkeypatch):
    bridge, boards = _bridge(monkeypatch)
    resolved = Path("/trusted/project")
    calls = []
    monkeypatch.setattr(bridge, "resolve_trusted_workspace", lambda value: calls.append(value) or resolved)

    created = bridge._create_board_payload({"slug": "project", "default_workdir": "  /saved/project  "})
    assert created["board"]["default_workdir"] == str(resolved)
    assert calls == ["/saved/project"]

    bridge._update_board_payload("project", {"name": "Project"})
    assert boards.boards["project"]["default_workdir"] == str(resolved)
    bridge._update_board_payload("project", {"default_workdir": ""})
    assert boards.boards["project"]["default_workdir"] == ""
    assert calls == ["/saved/project"]

    monkeypatch.setattr(bridge, "resolve_trusted_workspace", lambda value: (_ for _ in ()).throw(ValueError("untrusted")))
    try:
        bridge._update_board_payload("project", {"default_workdir": "/outside"})
    except ValueError as exc:
        assert "untrusted" in str(exc)
    else:
        raise AssertionError("untrusted workdir must be rejected")


def test_board_modal_submit_includes_and_clears_default_workdir():
    assert 'id="kanbanBoardModalDefaultWorkdir"' in INDEX
    default_workdir_input = re.search(
        r'<input\b[^>]*\bid="kanbanBoardModalDefaultWorkdir"[^>]*>', INDEX
    )
    assert default_workdir_input
    assert 'maxlength="255"' in default_workdir_input.group(0)
    assert 'id="kanbanBoardModalOriginalDefaultWorkdir"' in INDEX
    assert 'list="kanbanBoardModalWorkdirs"' in INDEX
    assert "if (defaultWorkdir) payload.default_workdir = defaultWorkdir;" in PANELS
    assert "default_workdir" not in PANELS[PANELS.find("createKanbanTask"):PANELS.find("createKanbanTask") + 5000]
    assert "async function _loadKanbanBoardWorkdirOptions(){\n  await loadWorkspaceList();" in PANELS

    submit_src = extract_function(PANELS, "submitKanbanBoardModal", prefix="async function")
    script = f"""
const calls = [];
const fields = {{
  kanbanBoardModalError: {{ textContent: '' }},
  kanbanBoardModalMode: {{ value: 'rename' }},
  kanbanBoardModalName: {{ value: 'Board name' }},
  kanbanBoardModalSlugInput: {{ value: 'default' }},
  kanbanBoardModalDesc: {{ value: 'Board description' }},
  kanbanBoardModalIcon: {{ value: '📋' }},
  kanbanBoardModalColor: {{ value: '#7aa2ff' }},
  kanbanBoardModalDefaultWorkdir: {{ value: '' }},
  kanbanBoardModalOriginalDefaultWorkdir: {{ value: '' }},
  kanbanBoardModalSubmit: {{ disabled: false }},
  kanbanBoardModalSlug: {{ value: 'default' }},
}};
const document = {{
  getElementById(id) {{
    if (!fields[id]) throw new Error('missing element ' + id);
    return fields[id];
  }},
}};
function t() {{ return null; }}
async function api(url, opts) {{
  calls.push({{ url, body: JSON.parse(opts.body) }});
  return {{}};
}}
function closeKanbanBoardModal() {{}}
async function loadKanbanBoards() {{}}
{submit_src}
async function runCase(currentValue, originalValue) {{
  calls.length = 0;
  fields.kanbanBoardModalDefaultWorkdir.value = currentValue;
  fields.kanbanBoardModalOriginalDefaultWorkdir.value = originalValue;
  await submitKanbanBoardModal();
  return calls[0].body;
}}
(async () => {{
  const unchanged = await runCase('/saved/project', '/saved/project');
  const changed = await runCase('/saved/project-next', '/saved/project');
  const cleared = await runCase('', '/saved/project');
  console.log(JSON.stringify({{ unchanged, changed, cleared }}));
}})().catch((err) => {{
  console.error(err && err.stack ? err.stack : String(err));
  process.exit(1);
}});
"""
    result = subprocess.run(["node", "-e", script], check=True, text=True, capture_output=True)
    payload = json.loads(result.stdout)
    assert payload["unchanged"] == {
        "name": "Board name",
        "description": "Board description",
        "icon": "📋",
        "color": "#7aa2ff",
    }
    assert payload["changed"]["default_workdir"] == "/saved/project-next"
    assert payload["cleared"]["default_workdir"] == ""


def test_default_board_settings_remain_reachable():
    assert "switcher.hidden = false;" in PANELS
    assert "const archiveDisabled = current === 'default';" in PANELS
    assert "if (current === 'default') return;  // default's slug is immutable" not in PANELS
    menu = PANELS[PANELS.find("const actions = `"):PANELS.find("const actions = `") + 1800]
    assert "openKanbanRenameBoard()" in menu
    assert "renameDisabled" not in menu


def test_new_board_keys_are_present_in_every_locale_block():
    blocks = re.findall(r"\n\s*(?:['\"][a-z]{2}(?:-[A-Z][A-Za-z]+)?['\"]|[a-z]{2}(?:-[A-Z]{2})?)\s*:\s*\{(.*?)\n\s*\},", I18N, re.S)
    assert len(blocks) >= 14
    for block in blocks:
        assert "kanban_board_settings:" in block
        assert "kanban_board_default_workdir:" in block
        assert "kanban_board_default_workdir_placeholder:" in block


def _locale_for_snippet(snippet):
    index = I18N.find(snippet)
    assert index != -1, f"missing snippet: {snippet}"
    locale = None
    for match in re.finditer(
        r"^\s*(?:'(?P<quoted>[a-z]{2}(?:-[A-Z][A-Za-z]+)?)'|(?P<plain>[a-z]{2}(?:-[A-Z]{2})?))\s*:\s*\{",
        I18N,
        re.M,
    ):
        if match.start() > index:
            break
        locale = match.group("quoted") or match.group("plain")
    assert locale, f"missing locale opener before snippet: {snippet}"
    return locale


def test_new_board_keys_are_localized_in_non_english_sample_locales():
    samples = {
        "es": "Ruta de workspace predeterminada",
        "de": "Standard-Workspace-Pfad",
        "pt": "Caminho padrão do workspace",
        "ko": "기본 워크스페이스 경로",
    }
    for locale, expected in samples.items():
        snippet = f"kanban_board_default_workdir: '{expected}'"
        assert _locale_for_snippet(snippet) == locale
