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

from __future__ import annotations

import json
import logging
from pathlib import Path


def _write_facet(journal: Path, facet: str) -> None:
    facet_path = journal / "facets" / facet / "facet.json"
    facet_path.parent.mkdir(parents=True, exist_ok=True)
    facet_path.write_text(
        json.dumps({"title": facet.title(), "description": ""}),
        encoding="utf-8",
    )


def _write_detected_entities(
    journal: Path,
    facet: str,
    day: str,
    rows: list[dict],
) -> None:
    path = journal / "facets" / facet / "entities" / f"{day}.jsonl"
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(
        "".join(json.dumps(row, ensure_ascii=False) + "\n" for row in rows),
        encoding="utf-8",
    )


def test_schedule_post_process_writes_record_and_resolves_entities(
    tmp_path,
    monkeypatch,
):
    from solstone.talent.schedule import post_process
    from solstone.think.activities import load_activity_records

    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    _write_facet(tmp_path, "work")
    _write_detected_entities(
        tmp_path,
        "work",
        "20260420",
        [
            {"id": "yuri_namikawa", "type": "Person", "name": "Yuri Namikawa"},
            {"id": "scott_ward", "type": "Person", "name": "Scott Ward"},
        ],
    )

    payload = [
        {
            "activity": "meeting",
            "target_date": "2026-04-20",
            "start": "16:30:00",
            "end": "17:30:00",
            "title": "Yuri Namikawa intro call",
            "description": "Intro call with Yuri from Offline Ventures.",
            "details": "Google Meet",
            "participation": [
                {
                    "name": "Yuri Namikawa",
                    "role": "attendee",
                    "source": "screen",
                    "confidence": 0.95,
                    "context": "calendar invite",
                },
                {
                    "name": "Scott Ward",
                    "role": "mentioned",
                    "source": "screen",
                    "confidence": 0.5,
                    "context": "mentioned in notes",
                },
                {
                    "name": "Unknown Guest",
                    "role": "attendee",
                    "source": "screen",
                    "confidence": 0.4,
                    "context": "guest field",
                },
            ],
            "participation_confidence": 0.88,
            "facet": "work",
            "cancelled": False,
        }
    ]

    assert post_process(json.dumps(payload), {"day": "20260418"}) is None

    records = load_activity_records("work", "20260420", include_hidden=True)
    assert len(records) == 1
    record = records[0]
    assert record["id"] == "anticipated_meeting_163000_0420"
    assert record["source"] == "anticipated"
    assert record["active_entities"] == ["yuri_namikawa"]
    assert record["cancelled"] is False
    assert record["hidden"] is False
    assert record["participation_confidence"] == 0.88
    assert record["participation"][0]["entity_id"] == "yuri_namikawa"
    assert record["participation"][1]["entity_id"] == "scott_ward"
    assert record["participation"][2]["entity_id"] is None
    assert record["edits"][-1]["actor"] == "schedule"
    assert record["edits"][-1]["note"] == "created by schedule"


def test_schedule_ambiguous_participant_keeps_unresolved_form(
    tmp_path,
    monkeypatch,
):
    from solstone.talent.schedule import post_process
    from solstone.think.activities import load_activity_records
    from solstone.think.entities import (
        ResolutionScope,
        load_ambiguities,
        load_entities,
        record_ambiguity_choice,
    )

    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    _write_facet(tmp_path, "work")
    _write_detected_entities(
        tmp_path,
        "work",
        "20260420",
        [
            {"id": "sarah_connor", "type": "Person", "name": "Sarah Connor"},
            {"id": "sarah_lee", "type": "Person", "name": "Sarah Lee"},
        ],
    )

    payload = [
        {
            "activity": "meeting",
            "target_date": "2026-04-20",
            "start": "16:30:00",
            "end": "17:30:00",
            "title": "Sarah intro call",
            "description": "Intro call with Sarah.",
            "details": "Google Meet",
            "participation": [
                {
                    "name": "Sarah",
                    "role": "attendee",
                    "source": "screen",
                    "confidence": 0.95,
                    "context": "calendar invite",
                }
            ],
            "participation_confidence": 0.88,
            "facet": "work",
            "cancelled": False,
        }
    ]

    assert post_process(json.dumps(payload), {"day": "20260418"}) is None

    records = load_activity_records("work", "20260420", include_hidden=True)
    assert len(records) == 1
    record = records[0]
    assert record["active_entities"] == []
    assert record["participation"][0]["entity_id"] is None
    rows = load_ambiguities()
    assert len(rows) == 1
    assert rows[0]["normalized_query"] == "sarah"

    record_ambiguity_choice(
        "Sarah",
        "sarah_connor",
        load_entities("work", "20260420"),
        scope=ResolutionScope.facet_scope("work"),
    )
    resolved_payload = json.loads(json.dumps(payload))
    resolved_payload[0]["start"] = "17:00:00"
    resolved_payload[0]["end"] = "18:00:00"
    resolved_payload[0]["title"] = "Sarah follow-up call"
    assert post_process(json.dumps(resolved_payload), {"day": "20260418"}) is None

    records = load_activity_records("work", "20260420", include_hidden=True)
    assert len(records) == 2
    record = records[1]
    assert record["active_entities"] == ["sarah_connor"]
    assert record["participation"][0]["entity_id"] == "sarah_connor"
    rows = load_ambiguities()
    assert len(rows) == 1
    assert rows[0]["status"] == "resolved"


def test_schedule_post_process_accepts_wrapped_events(tmp_path, monkeypatch):
    from solstone.talent.schedule import post_process
    from solstone.think.activities import load_activity_records

    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    _write_facet(tmp_path, "work")

    payload = {
        "events": [
            {
                "activity": "meeting",
                "target_date": "2026-04-23",
                "start": "09:00:00",
                "end": "09:30:00",
                "title": "Planning call",
                "description": "Planning call with the team.",
                "details": "Google Meet",
                "participation": [],
                "participation_confidence": 0.8,
                "facet": "work",
                "cancelled": False,
            }
        ]
    }

    post_process(json.dumps(payload), {"day": "20260418"})

    records = load_activity_records("work", "20260423", include_hidden=True)
    assert len(records) == 1
    assert records[0]["id"] == "anticipated_meeting_090000_0423"
    assert records[0]["source"] == "anticipated"


def test_schedule_post_process_marks_cancelled_records_hidden(tmp_path, monkeypatch):
    from solstone.talent.schedule import post_process
    from solstone.think.activities import load_activity_records

    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    _write_facet(tmp_path, "work")

    payload = [
        {
            "activity": "meeting",
            "target_date": "2026-04-22",
            "start": "09:00:00",
            "end": "10:00:00",
            "title": "Scott Ward standup",
            "description": "Weekly standup with Scott Ward.",
            "details": "Recurring invite",
            "participation": [],
            "participation_confidence": 0.85,
            "facet": "work",
            "cancelled": True,
        }
    ]

    post_process(json.dumps(payload), {"day": "20260418"})

    records = load_activity_records("work", "20260422", include_hidden=True)
    assert len(records) == 1
    record = records[0]
    assert record["cancelled"] is True
    assert record["hidden"] is True
    assert record["edits"][-1]["note"] == "created by schedule (cancelled on calendar)"


def test_schedule_post_process_skips_missing_required_field(
    tmp_path,
    monkeypatch,
    caplog,
):
    from solstone.talent.schedule import post_process
    from solstone.think.activities import load_activity_records

    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    _write_facet(tmp_path, "work")
    caplog.set_level(logging.WARNING, logger="solstone.talent.schedule")

    post_process(
        json.dumps(
            [
                {
                    "activity": "meeting",
                    "target_date": "2026-04-20",
                    "start": "09:00:00",
                    "end": None,
                    "description": "Missing title should fail.",
                    "details": "",
                    "participation": [],
                    "participation_confidence": 0.5,
                    "facet": "work",
                    "cancelled": False,
                }
            ]
        ),
        {"day": "20260418"},
    )

    assert load_activity_records("work", "20260420", include_hidden=True) == []
    assert "missing required field 'title'" in caplog.text


def test_schedule_post_process_skips_unknown_facet(tmp_path, monkeypatch, caplog):
    from solstone.talent.schedule import post_process

    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    _write_facet(tmp_path, "work")
    caplog.set_level(logging.WARNING, logger="solstone.talent.schedule")

    post_process(
        json.dumps(
            [
                {
                    "activity": "meeting",
                    "target_date": "2026-04-20",
                    "start": "09:00:00",
                    "end": None,
                    "title": "Wrong facet",
                    "description": "This facet should be rejected.",
                    "details": "",
                    "participation": [],
                    "participation_confidence": 0.5,
                    "facet": "missing",
                    "cancelled": False,
                }
            ]
        ),
        {"day": "20260418"},
    )

    assert "unknown facet 'missing'" in caplog.text


def test_schedule_post_process_skips_empty_facet_from_zero_facet_hydration(
    tmp_path, monkeypatch, caplog
):
    from jsonschema import Draft202012Validator

    from solstone.talent.schedule import post_process
    from solstone.think.activities import load_activity_records
    from solstone.think.talent import hydrate_runtime_enums

    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    monkeypatch.setattr("solstone.think.talent._valid_runtime_facets", lambda: [])
    _write_facet(tmp_path, "work")
    caplog.set_level(logging.WARNING, logger="solstone.talent.schedule")
    schema_path = (
        Path(__file__).resolve().parents[1]
        / "solstone"
        / "talent"
        / "schedule.schema.json"
    )
    schema = json.loads(schema_path.read_text(encoding="utf-8"))
    hydrated_schema = hydrate_runtime_enums(schema)
    event = {
        "activity": "meeting",
        "target_date": "2026-04-20",
        "start": "09:00:00",
        "end": None,
        "title": "Empty facet",
        "description": "This facet should be rejected by the hook.",
        "details": "",
        "participation": [],
        "participation_confidence": 0.5,
        "facet": "",
        "cancelled": False,
    }
    payload = {"events": [event]}

    assert list(Draft202012Validator(hydrated_schema).iter_errors(payload)) == []
    assert post_process(json.dumps(payload), {"day": "20260418"}) is None

    assert load_activity_records("work", "20260420", include_hidden=True) == []
    assert "missing required field 'facet'" in caplog.text


def test_schedule_post_process_skips_non_future_items(tmp_path, monkeypatch, caplog):
    from solstone.talent.schedule import post_process
    from solstone.think.activities import load_activity_records

    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    _write_facet(tmp_path, "work")
    caplog.set_level(logging.WARNING, logger="solstone.talent.schedule")

    post_process(
        json.dumps(
            [
                {
                    "activity": "meeting",
                    "target_date": "2026-04-18",
                    "start": "09:00:00",
                    "end": None,
                    "title": "Too soon",
                    "description": "Should be dropped because it is not future-dated.",
                    "details": "",
                    "participation": [],
                    "participation_confidence": 0.5,
                    "facet": "work",
                    "cancelled": False,
                }
            ]
        ),
        {"day": "20260418"},
    )

    assert load_activity_records("work", "20260418", include_hidden=True) == []
    assert "target_date must be after context day" in caplog.text


def test_schedule_post_process_logs_error_on_invalid_json(
    tmp_path, monkeypatch, caplog
):
    from solstone.talent.schedule import post_process

    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    _write_facet(tmp_path, "work")
    caplog.set_level(logging.ERROR, logger="solstone.talent.schedule")

    assert post_process("{not valid json", {"day": "20260418"}) is None
    assert "failed to parse JSON" in caplog.text


def test_schedule_post_process_is_idempotent(tmp_path, monkeypatch):
    from solstone.talent.schedule import post_process
    from solstone.think.activities import load_activity_records

    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    _write_facet(tmp_path, "work")

    payload = json.dumps(
        [
            {
                "activity": "call",
                "target_date": "2026-04-21",
                "start": "10:30:00",
                "end": "11:00:00",
                "title": "Mari Zumbro intro",
                "description": "First call with Mari Zumbro.",
                "details": "Google Meet",
                "participation": [],
                "participation_confidence": 0.9,
                "facet": "work",
                "cancelled": False,
            }
        ]
    )

    post_process(payload, {"day": "20260418"})
    post_process(payload, {"day": "20260418"})

    records = load_activity_records("work", "20260421", include_hidden=True)
    assert len(records) == 1
    assert records[0]["id"] == "anticipated_call_103000_0421"


def test_schedule_post_process_fuzzy_supersedes_previous_record(tmp_path, monkeypatch):
    from solstone.talent.schedule import post_process
    from solstone.think.activities import append_activity_record, load_activity_records

    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    _write_facet(tmp_path, "work")

    append_activity_record(
        "work",
        "20260421",
        {
            "id": "anticipated_call_100000_0421",
            "activity": "call",
            "target_date": "2026-04-21",
            "start": "10:00:00",
            "end": "10:30:00",
            "title": "Mari Zumbro intro",
            "description": "Old version",
            "details": "",
            "facet": "work",
            "source": "anticipated",
            "participation": [],
            "active_entities": [],
            "participation_confidence": 0.8,
            "cancelled": False,
            "hidden": False,
        },
    )

    post_process(
        json.dumps(
            [
                {
                    "activity": "call",
                    "target_date": "2026-04-21",
                    "start": "10:30:00",
                    "end": "11:00:00",
                    "title": "Mari Zumbro intro",
                    "description": "Updated invite",
                    "details": "Google Meet",
                    "participation": [],
                    "participation_confidence": 0.9,
                    "facet": "work",
                    "cancelled": False,
                }
            ]
        ),
        {"day": "20260418"},
    )

    records = {
        record["id"]: record
        for record in load_activity_records("work", "20260421", include_hidden=True)
    }
    assert set(records) == {
        "anticipated_call_100000_0421",
        "anticipated_call_103000_0421",
    }
    assert records["anticipated_call_100000_0421"]["hidden"] is True
    assert (
        records["anticipated_call_100000_0421"]["edits"][-1]["note"]
        == "superseded by anticipated_call_103000_0421"
    )
    assert records["anticipated_call_103000_0421"]["hidden"] is False


def test_schedule_post_process_cancelled_record_supersedes_pending(
    tmp_path,
    monkeypatch,
):
    from solstone.talent.schedule import post_process
    from solstone.think.activities import append_activity_record, load_activity_records

    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    _write_facet(tmp_path, "work")

    append_activity_record(
        "work",
        "20260424",
        {
            "id": "anticipated_meeting_090000_0424",
            "activity": "meeting",
            "target_date": "2026-04-24",
            "start": "09:00:00",
            "end": "10:00:00",
            "title": "Scott Ward standup",
            "description": "Pending version",
            "details": "",
            "facet": "work",
            "source": "anticipated",
            "participation": [],
            "active_entities": [],
            "participation_confidence": 0.85,
            "cancelled": False,
            "hidden": False,
        },
    )

    post_process(
        json.dumps(
            [
                {
                    "activity": "meeting",
                    "target_date": "2026-04-24",
                    "start": "09:30:00",
                    "end": "10:00:00",
                    "title": "Scott Ward standup",
                    "description": "Calendar now shows it cancelled.",
                    "details": "Recurring invite",
                    "participation": [],
                    "participation_confidence": 0.85,
                    "facet": "work",
                    "cancelled": True,
                }
            ]
        ),
        {"day": "20260418"},
    )

    records = {
        record["id"]: record
        for record in load_activity_records("work", "20260424", include_hidden=True)
    }
    assert set(records) == {
        "anticipated_meeting_090000_0424",
        "anticipated_meeting_093000_0424",
    }
    assert records["anticipated_meeting_090000_0424"]["hidden"] is True
    assert records["anticipated_meeting_093000_0424"]["hidden"] is True
    assert (
        records["anticipated_meeting_090000_0424"]["edits"][-1]["note"]
        == "superseded by anticipated_meeting_093000_0424"
    )
    assert (
        records["anticipated_meeting_093000_0424"]["edits"][-1]["note"]
        == "created by schedule (cancelled on calendar)"
    )
