# standard
import json
from datetime import datetime, timezone
from pathlib import Path

# third party
# custom
from ...conftest import ok, err
from sunwaee.cli import app

WS = "personal"
_BASE = ["--workspace", WS]


### HELPERS


def _today_logs_dir(tmp_path: Path, workspace: str = "personal") -> Path:
    now = datetime.now(timezone.utc)
    return (
        tmp_path
        / "workspaces"
        / workspace
        / "logs"
        / str(now.year)
        / f"{now.month:02d}"
        / f"{now.day:02d}"
    )


def _read_today_entries(tmp_path: Path, workspace: str = "personal") -> list[dict]:
    log_file = _today_logs_dir(tmp_path, workspace) / "logs.jsonl"
    if not log_file.exists():
        return []
    return [
        json.loads(line) for line in log_file.read_text().splitlines() if line.strip()
    ]


### AUDIT INTEGRATION


def test_note_create_writes_log(runner, ws, env):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "Logged Note",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    entries = _read_today_entries(env)
    assert any(
        e["module"] == "notes"
        and e["operation"] == "create"
        and e["item_title"] == "Logged Note"
        for e in entries
    )


def test_note_edit_writes_log(runner, ws, env):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "Edit me",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    runner.invoke(
        app,
        ["notes", "update", "Edit me", "--body", "updated", "--workspace", "personal"],
    )
    entries = _read_today_entries(env)
    assert any(e["module"] == "notes" and e["operation"] == "update" for e in entries)


def test_note_delete_writes_log(runner, ws, env):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "Delete me",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    runner.invoke(
        app, ["notes", "delete", "Delete me", "--confirm", "--workspace", "personal"]
    )
    entries = _read_today_entries(env)
    assert any(
        e["module"] == "notes"
        and e["operation"] == "delete"
        and e["item_title"] == "Delete me"
        for e in entries
    )


def test_task_create_writes_log(runner, ws, env):
    runner.invoke(
        app, ["tasks", "create", "--title", "My Task", "--workspace", "personal"]
    )
    entries = _read_today_entries(env)
    assert any(
        e["module"] == "tasks"
        and e["operation"] == "create"
        and e["item_title"] == "My Task"
        for e in entries
    )


def test_task_update_writes_log(runner, ws, env):
    runner.invoke(
        app, ["tasks", "create", "--title", "Edit task", "--workspace", "personal"]
    )
    runner.invoke(
        app,
        [
            "tasks",
            "update",
            "Edit task",
            "--title",
            "Edited task",
            "--workspace",
            "personal",
        ],
    )
    entries = _read_today_entries(env)
    assert any(e["module"] == "tasks" and e["operation"] == "update" for e in entries)


def test_task_delete_writes_log(runner, ws, env):
    runner.invoke(
        app, ["tasks", "create", "--title", "Drop me", "--workspace", "personal"]
    )
    runner.invoke(
        app, ["tasks", "delete", "Drop me", "--workspace", "personal"]
    )
    entries = _read_today_entries(env)
    assert any(
        e["module"] == "tasks"
        and e["operation"] == "delete"
        and e["item_title"] == "Drop me"
        for e in entries
    )


def test_workspace_create_writes_log(runner, env):
    runner.invoke(app, ["workspaces", "create", "newws"])
    entries = _read_today_entries(env, workspace="newws")
    assert any(
        e["module"] == "workspaces" and e["operation"] == "create" for e in entries
    )


def test_workspace_update_writes_log(runner, ws, env):
    runner.invoke(app, ["workspaces", "update", "personal", "--body", "x"])
    entries = _read_today_entries(env)
    assert any(
        e["module"] == "workspaces" and e["operation"] == "update" for e in entries
    )


def test_workspace_delete_writes_log(runner, ws, env):
    runner.invoke(app, ["workspaces", "create", "todelete"])
    runner.invoke(app, ["workspaces", "delete", "todelete", "--confirm"])
    # log was written before rmtree, file is gone — check personal ws which still exists
    # the log was written to todelete/logs, which is now deleted; verify no crash occurred
    result = runner.invoke(app, ["workspaces", "list"])
    assert result.exit_code == 0


def test_workspace_read_writes_log(runner, ws, env):
    runner.invoke(app, ["workspaces", "read", "personal"])
    entries = _read_today_entries(env)
    assert any(
        e["module"] == "workspaces" and e["operation"] == "read" for e in entries
    )


def test_workspace_list_writes_log(runner, ws, env):
    runner.invoke(app, ["workspaces", "list"])
    entries = _read_today_entries(env)
    assert any(
        e["module"] == "workspaces" and e["operation"] == "list" for e in entries
    )


def test_project_read_writes_log(runner, ws, env):
    runner.invoke(app, ["projects", "create", "myproject", "--workspace", "personal"])
    runner.invoke(app, ["projects", "read", "myproject", "--workspace", "personal"])
    entries = _read_today_entries(env)
    assert any(
        e["module"] == "projects" and e["operation"] == "read" for e in entries
    )


def test_project_list_writes_log(runner, ws, env):
    runner.invoke(app, ["projects", "list", "--workspace", "personal"])
    entries = _read_today_entries(env)
    assert any(
        e["module"] == "projects" and e["operation"] == "list" for e in entries
    )


def test_note_read_writes_log(runner, ws, env):
    runner.invoke(
        app,
        ["notes", "create", "--title", "Readable", "--body", "x", "--workspace", "personal"],
    )
    runner.invoke(app, ["notes", "read", "Readable", "--workspace", "personal"])
    entries = _read_today_entries(env)
    assert any(e["module"] == "notes" and e["operation"] == "read" for e in entries)


def test_task_read_writes_log(runner, ws, env):
    runner.invoke(
        app, ["tasks", "create", "--title", "Readable Task", "--workspace", "personal"]
    )
    runner.invoke(app, ["tasks", "read", "Readable Task", "--workspace", "personal"])
    entries = _read_today_entries(env)
    assert any(e["module"] == "tasks" and e["operation"] == "read" for e in entries)


def test_log_read_writes_log(runner, ws, env):
    runner.invoke(
        app,
        ["notes", "create", "--title", "Seed", "--body", "x", "--workspace", "personal"],
    )
    entries = _read_today_entries(env)
    entry_id = entries[0]["id"]
    runner.invoke(app, ["logs", "read", entry_id, "--workspace", "personal"])
    entries_after = _read_today_entries(env)
    assert any(e["module"] == "logs" and e["operation"] == "read" for e in entries_after)


def test_note_list_writes_log(runner, ws, env):
    runner.invoke(app, ["notes", "list", *_BASE])
    entries = _read_today_entries(env)
    assert any(e["module"] == "notes" and e["operation"] == "list" for e in entries)


def test_note_list_log_stores_args(runner, ws, env):
    runner.invoke(app, ["notes", "create", "--title", "X", "--workspace", "personal"])
    runner.invoke(app, ["notes", "list", "--query", "x", *_BASE])
    entries = _read_today_entries(env)
    list_entry = next(
        e for e in entries if e["module"] == "notes" and e["operation"] == "list"
    )
    assert list_entry["args"]["query"] == "x"
    assert "results" in list_entry["args"]


def test_task_list_writes_log(runner, ws, env):
    runner.invoke(app, ["tasks", "list", *_BASE])
    entries = _read_today_entries(env)
    assert any(e["module"] == "tasks" and e["operation"] == "list" for e in entries)


def test_task_list_log_stores_args(runner, ws, env):
    runner.invoke(app, ["tasks", "create", "--title", "T", "--workspace", "personal"])
    runner.invoke(app, ["tasks", "list", "--status", "todo", *_BASE])
    entries = _read_today_entries(env)
    list_entry = next(
        e for e in entries if e["module"] == "tasks" and e["operation"] == "list"
    )
    assert list_entry["args"]["status"] == "todo"
    assert "results" in list_entry["args"]


def test_log_list_writes_log(runner, ws, env):
    runner.invoke(app, ["logs", "list", *_BASE])
    entries = _read_today_entries(env)
    assert any(e["module"] == "logs" and e["operation"] == "list" for e in entries)


def test_log_list_log_stores_args(runner, ws, env):
    runner.invoke(app, ["logs", "list", "--module", "notes", *_BASE])
    entries = _read_today_entries(env)
    list_entry = next(
        e for e in entries if e["module"] == "logs" and e["operation"] == "list"
    )
    assert list_entry["args"]["module"] == "notes"
    assert "results" in list_entry["args"]


def test_log_entry_has_caller_field(runner, ws, env):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "Caller check",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    entries = _read_today_entries(env)
    matching = [e for e in entries if e.get("item_title") == "Caller check"]
    assert matching
    assert matching[0]["caller"] == "api"


### LIST COMMAND


def test_log_list_empty(runner, env):
    data = ok(runner.invoke(app, ["logs", "list", *_BASE]))
    assert data == []


def test_log_list_returns_todays_entries(runner, ws):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "Listed",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    data = ok(runner.invoke(app, ["logs", "list", *_BASE]))
    assert any(e["item_title"] == "Listed" for e in data)


def test_log_list_entry_fields(runner, ws):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "Fields check",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    data = ok(runner.invoke(app, ["logs", "list", *_BASE]))
    entry = next(e for e in data if e.get("item_title") == "Fields check")
    assert "id" in entry
    assert "timestamp" in entry
    assert "caller" in entry
    assert "module" in entry
    assert "operation" in entry
    assert "ok" in entry


def test_log_list_filter_by_module(runner, ws):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "A note",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    runner.invoke(
        app, ["tasks", "create", "--title", "A task", "--workspace", "personal"]
    )
    data = ok(
        runner.invoke(
            app, ["logs", "list", *_BASE, "--module", "notes"]
        )
    )
    assert all(e["module"] == "notes" for e in data)


def test_log_list_filter_by_operation(runner, ws):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "Op filter",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    runner.invoke(
        app, ["notes", "update", "Op filter", "--body", "new", "--workspace", "personal"]
    )
    data = ok(
        runner.invoke(
            app, ["logs", "list", *_BASE, "--operation", "create"]
        )
    )
    assert all(e["operation"] == "create" for e in data)


def test_log_list_filter_by_caller(runner, ws):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "Caller filter",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    data = ok(
        runner.invoke(
            app, ["logs", "list", *_BASE, "--caller", "api"]
        )
    )
    assert all(e["caller"] == "api" for e in data)


def test_log_list_with_date_today(runner, ws):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "Date filter",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    data = ok(
        runner.invoke(app, ["logs", "list", *_BASE, "--date", today])
    )
    assert any(e.get("item_title") == "Date filter" for e in data)


def test_log_list_with_date_no_entries(runner, ws):
    data = ok(
        runner.invoke(
            app, ["logs", "list", *_BASE, "--date", "2000-01-01"]
        )
    )
    assert data == []


def test_log_list_with_month(runner, ws):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "Month filter",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    month = datetime.now(timezone.utc).strftime("%Y-%m")
    data = ok(
        runner.invoke(
            app, ["logs", "list", *_BASE, "--month", month]
        )
    )
    assert any(e.get("item_title") == "Month filter" for e in data)


def test_log_list_with_all(runner, ws):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "All logs",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    data = ok(runner.invoke(app, ["logs", "list", *_BASE, "--all"]))
    assert any(e.get("item_title") == "All logs" for e in data)


def test_log_list_limit(runner, ws):
    for i in range(5):
        runner.invoke(
            app,
            [
                "notes",
                "create",
                "--title",
                f"Note {i}",
                "--body",
                "x",
                "--workspace",
                "personal",
            ],
        )
    data = ok(
        runner.invoke(app, ["logs", "list", *_BASE, "--limit", "2"])
    )
    assert len(data) <= 2


def test_log_list_sorted_newest_first(runner, ws):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "First",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "Second",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    data = ok(runner.invoke(app, ["logs", "list", *_BASE]))
    timestamps = [e["timestamp"] for e in data]
    assert timestamps == sorted(timestamps, reverse=True)


def test_log_list_multiple_date_flags_error(runner, ws):
    today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    month = datetime.now(timezone.utc).strftime("%Y-%m")
    e = err(
        runner.invoke(
            app,
            [
                "logs",
                "list",
                "--date",
                today,
                "--month",
                month,
                *_BASE,
            ],
        )
    )
    assert e["code"] == "VALIDATION_ERROR"


def test_log_list_invalid_date_format(runner, ws):
    e = err(
        runner.invoke(
            app, ["logs", "list", "--date", "not-a-date", *_BASE]
        )
    )
    assert e["code"] == "VALIDATION_ERROR"


def test_log_list_invalid_month_format(runner, ws):
    e = err(
        runner.invoke(
            app, ["logs", "list", "--month", "2026/03", *_BASE]
        )
    )
    assert e["code"] == "VALIDATION_ERROR"


def test_log_list_all_workspaces(runner, ws):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "WS log",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    data = ok(runner.invoke(app, ["logs", "list"]))
    assert any(e.get("item_title") == "WS log" for e in data)
    assert all("workspace" in e for e in data)


def test_log_list_query_by_item_title(runner, ws):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "Queryable Note",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    runner.invoke(
        app, ["tasks", "create", "--title", "Unrelated Task", "--workspace", "personal"]
    )
    data = ok(
        runner.invoke(
            app, ["logs", "list", "--query", "queryable", *_BASE]
        )
    )
    assert len(data) >= 1
    assert all("queryable" in (e.get("item_title") or "").lower() for e in data)


def test_log_list_query_by_module(runner, ws):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "A note",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    runner.invoke(
        app, ["tasks", "create", "--title", "A task", "--workspace", "personal"]
    )
    data = ok(
        runner.invoke(
            app, ["logs", "list", "--query", "notes", *_BASE]
        )
    )
    assert all(e["module"] == "notes" for e in data)


def test_log_list_query_no_match(runner, ws):
    runner.invoke(
        app,
        [
            "notes",
            "create",
            "--title",
            "Something",
            "--body",
            "x",
            "--workspace",
            "personal",
        ],
    )
    data = ok(
        runner.invoke(
            app, ["logs", "list", "--query", "zzznomatch", *_BASE]
        )
    )
    assert data == []
