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

import json
import re
from datetime import UTC, datetime

from solstone.think.surfaces.types import Cadence


def _configure_env(tmp_path, monkeypatch) -> None:
    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
    monkeypatch.setenv("SOL_SKIP_SUPERVISOR_CHECK", "1")

    import solstone.think.utils as think_utils

    think_utils._journal_path_cache = None


def _write_journal_entity(
    tmp_path,
    entity_id: str,
    name: str,
    *,
    entity_type: str = "Person",
    aka: list[str] | None = None,
    is_principal: bool = False,
) -> None:
    entity_dir = tmp_path / "entities" / entity_id
    entity_dir.mkdir(parents=True, exist_ok=True)
    payload: dict[str, object] = {"id": entity_id, "name": name, "type": entity_type}
    if aka:
        payload["aka"] = aka
    if is_principal:
        payload["is_principal"] = True
    (entity_dir / "entity.json").write_text(json.dumps(payload), encoding="utf-8")


def _write_facet_relationship(
    tmp_path, facet: str, entity_id: str, *, description: str = ""
) -> None:
    relationship_dir = tmp_path / "facets" / facet / "entities" / entity_id
    relationship_dir.mkdir(parents=True, exist_ok=True)
    (relationship_dir / "entity.json").write_text(
        json.dumps({"entity_id": entity_id, "description": description}),
        encoding="utf-8",
    )


def _minimal_facet_tree(
    tmp_path,
    facets=("work",),
    *,
    muted_facets=(),
    journal_entities: tuple[dict[str, object], ...] = (),
) -> None:
    for facet in facets:
        facet_dir = tmp_path / "facets" / facet
        facet_dir.mkdir(parents=True, exist_ok=True)
        (facet_dir / "activities").mkdir(exist_ok=True)
        (facet_dir / "facet.json").write_text(
            json.dumps(
                {
                    "title": facet.title(),
                    "description": "",
                    "color": "",
                    "emoji": "",
                    "muted": facet in set(muted_facets),
                }
            ),
            encoding="utf-8",
        )

    for entity in journal_entities:
        _write_journal_entity(
            tmp_path,
            str(entity["id"]),
            str(entity["name"]),
            entity_type=str(entity.get("type", "Person")),
            aka=list(entity.get("aka", []))
            if isinstance(entity.get("aka"), list)
            else None,
            is_principal=bool(entity.get("is_principal", False)),
        )


def _utc_ms(day: str, hour: int = 12) -> int:
    parsed = datetime.strptime(f"{day} {hour:02d}:00:00", "%Y%m%d %H:%M:%S")
    return int(parsed.replace(tzinfo=UTC).timestamp() * 1000)


def _participant(
    entity_id: str | None,
    *,
    name: str | None = None,
    role: str = "attendee",
    source: str = "screen",
    confidence: float = 0.9,
    context: str = "test participant",
) -> dict[str, object]:
    display_name = name or (
        entity_id.replace("_", " ").title() if entity_id else "Unknown"
    )
    return {
        "name": display_name,
        "role": role,
        "source": source,
        "confidence": confidence,
        "context": context,
        "entity_id": entity_id,
    }


def _activity_record(
    day: str, participation: list[dict[str, object]], **kwargs
) -> dict:
    activity = str(kwargs.pop("activity", "meeting"))
    record_id = str(kwargs.pop("record_id", f"{activity}_{day}_120000"))
    title = str(kwargs.pop("title", f"{record_id} title"))
    record = {
        "id": record_id,
        "activity": activity,
        "title": title,
        "description": str(kwargs.pop("description", title)),
        "details": str(kwargs.pop("details", "")),
        "participation": participation,
        "segments": list(kwargs.pop("segments", [])),
        "active_entities": list(kwargs.pop("active_entities", [])),
        "created_at": int(kwargs.pop("created_at", _utc_ms(day))),
        "source": str(kwargs.pop("source", "user")),
        "hidden": bool(kwargs.pop("hidden", False)),
        "edits": list(kwargs.pop("edits", [])),
    }
    record.update(kwargs)
    return record


def _append_activity(facet: str, day: str, record: dict) -> None:
    from solstone.think.activities import append_activity_record

    append_activity_record(facet, day, record)


def _commitment(
    *,
    owner: str = "Mina",
    owner_entity_id: str | None = "mina",
    action: str = "send proposal",
    counterparty: str = "Ravi",
    counterparty_entity_id: str | None = "ravi",
    when: str = "tomorrow",
    context: str = "Commitment context.",
) -> dict[str, object]:
    return {
        "owner": owner,
        "owner_entity_id": owner_entity_id,
        "action": action,
        "counterparty": counterparty,
        "counterparty_entity_id": counterparty_entity_id,
        "when": when,
        "context": context,
    }


def _decision(
    *,
    owner: str = "Ravi",
    owner_entity_id: str | None = "ravi",
    action: str = "move launch review",
    context: str = "Decision context.",
) -> dict[str, object]:
    return {
        "owner": owner,
        "owner_entity_id": owner_entity_id,
        "action": action,
        "context": context,
    }


def _write_story_activity(
    facet: str,
    day: str,
    record_id: str,
    created_at: int,
    *,
    commitments: list[dict[str, object]] | None = None,
    closures: list[dict[str, object]] | None = None,
    decisions: list[dict[str, object]] | None = None,
) -> None:
    from solstone.think.activities import append_activity_record, merge_story_fields

    append_activity_record(
        facet,
        day,
        _activity_record(
            day,
            [],
            record_id=record_id,
            created_at=created_at,
            title=f"{record_id} title",
        ),
    )
    merge_story_fields(
        facet,
        day,
        record_id,
        story={
            "talent": "story",
            "body": f"{record_id} summary",
            "topics": ["profile"],
            "confidence": 0.9,
        },
        commitments=commitments or [],
        closures=closures or [],
        decisions=decisions or [],
        relations=[],
        actor="story",
    )


def test_cadence_zero_interactions(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},),
    )
    _write_facet_relationship(tmp_path, "work", "ravi", description="Customer")

    assert profile_surface.cadence("Ravi") == Cadence(0, None, None, None)


def test_cadence_single_day(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},),
    )
    _write_facet_relationship(tmp_path, "work", "ravi", description="Customer")
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")

    _append_activity(
        "work",
        "20260418",
        _activity_record(
            "20260418",
            [_participant("ravi", name="Ravi")],
            record_id="meeting_20260418_a",
        ),
    )

    cadence = profile_surface.cadence("Ravi")
    assert cadence == Cadence(1, "20260418", None, None)


def test_cadence_multi_day(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},),
    )
    _write_facet_relationship(tmp_path, "work", "ravi", description="Customer")
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")

    for index, day in enumerate(["20260410", "20260414", "20260418"], start=1):
        _append_activity(
            "work",
            day,
            _activity_record(
                day,
                [_participant("ravi", name="Ravi")],
                record_id=f"meeting_{index}",
            ),
        )

    cadence = profile_surface.cadence("Ravi")
    assert cadence is not None
    assert cadence.recent_interactions_count_30d == 3
    assert cadence.last_seen == "20260418"
    assert cadence.avg_interval_days == 4.0
    assert cadence.gone_quiet_since is None


def test_cadence_gone_quiet_threshold(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=(
            {"id": "quiet_person", "name": "Quiet Person", "type": "Person"},
            {"id": "boundary_person", "name": "Boundary Person", "type": "Person"},
        ),
    )
    _write_facet_relationship(tmp_path, "work", "quiet_person", description="Quiet")
    _write_facet_relationship(
        tmp_path, "work", "boundary_person", description="Boundary"
    )
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")

    for day in ["20260401", "20260406"]:
        _append_activity(
            "work",
            day,
            _activity_record(
                day,
                [_participant("quiet_person", name="Quiet Person")],
                record_id=f"quiet_{day}",
            ),
        )
    for day in ["20260405", "20260410"]:
        _append_activity(
            "work",
            day,
            _activity_record(
                day,
                [_participant("boundary_person", name="Boundary Person")],
                record_id=f"boundary_{day}",
            ),
        )

    quiet = profile_surface.cadence("Quiet Person")
    boundary = profile_surface.cadence("Boundary Person")

    assert quiet is not None
    assert quiet.gone_quiet_since == 14
    assert boundary is not None
    assert boundary.gone_quiet_since is None


def test_cadence_gone_quiet_returns_int_days(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=(
            {"id": "far_person", "name": "Far Person", "type": "Person"},
            {"id": "equal_person", "name": "Equal Person", "type": "Person"},
            {"id": "recent_person", "name": "Recent Person", "type": "Person"},
        ),
    )
    for entity_id, description in (
        ("far_person", "Far"),
        ("equal_person", "Equal"),
        ("recent_person", "Recent"),
    ):
        _write_facet_relationship(tmp_path, "work", entity_id, description=description)
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")

    for entity_id, days in (
        ("far_person", ("20260330", "20260404")),
        ("equal_person", ("20260405", "20260410")),
        ("recent_person", ("20260406", "20260411")),
    ):
        for day in days:
            _append_activity(
                "work",
                day,
                _activity_record(
                    day,
                    [_participant(entity_id, name=entity_id.replace("_", " ").title())],
                    record_id=f"{entity_id}_{day}",
                ),
            )

    far = profile_surface.cadence("Far Person")
    equal = profile_surface.cadence("Equal Person")
    recent = profile_surface.cadence("Recent Person")

    assert far is not None
    assert far.gone_quiet_since == 16
    assert equal is not None
    assert equal.gone_quiet_since is None
    assert recent is not None
    assert recent.gone_quiet_since is None


def test_cadence_distinct_days_vs_record_count(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},),
    )
    _write_facet_relationship(tmp_path, "work", "ravi", description="Customer")
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")

    for suffix in ("a", "b", "c"):
        _append_activity(
            "work",
            "20260418",
            _activity_record(
                "20260418",
                [_participant("ravi", name="Ravi")],
                record_id=f"meeting_20260418_{suffix}",
            ),
        )

    cadence = profile_surface.cadence("Ravi")
    assert cadence == Cadence(3, "20260418", None, None)


def test_resolve_exact_and_slug_and_aka_and_fuzzy(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=(
            {
                "id": "john_borthwick",
                "name": "John Borthwick",
                "type": "Person",
                "aka": ["JB"],
            },
        ),
    )
    _write_facet_relationship(
        tmp_path, "work", "john_borthwick", description="Investor"
    )

    queries = ["John Borthwick", "john_borthwick", "JB", "John Borthwik"]
    resolved_ids = {
        profile_surface._resolve_target(query).entity_id  # noqa: SLF001
        for query in queries
    }

    assert resolved_ids == {"john_borthwick"}


def test_facets_filter_narrows_display_not_cadence(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        facets=("personal", "work"),
        journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},),
    )
    _write_facet_relationship(tmp_path, "work", "ravi", description="Work contact")
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")
    _append_activity(
        "work",
        "20260418",
        _activity_record(
            "20260418",
            [_participant("ravi", name="Ravi")],
            record_id="meeting_20260418",
        ),
    )

    profile = profile_surface.full("Ravi", facets=["personal"])

    assert profile is not None
    assert profile.facets == ()
    assert profile.description is None
    assert profile.cadence.recent_interactions_count_30d == 1


def test_include_mentions_toggle(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},),
    )
    _write_facet_relationship(tmp_path, "work", "ravi", description="Customer")
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")
    _append_activity(
        "work",
        "20260418",
        _activity_record(
            "20260418",
            [_participant("ravi", name="Ravi", role="mentioned")],
            record_id="meeting_20260418",
        ),
    )

    assert profile_surface.cadence("Ravi") == Cadence(0, None, None, None)
    assert profile_surface.cadence("Ravi", include_mentions=True) == Cadence(
        1, "20260418", None, None
    )


def test_self_view_returns_is_self_true(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=(
            {
                "id": "romeo_montague",
                "name": "Romeo Montague",
                "type": "Person",
                "aka": ["RM"],
                "is_principal": True,
            },
        ),
    )
    _write_facet_relationship(tmp_path, "work", "romeo_montague", description="Founder")

    profile = profile_surface.full("RM")

    assert profile is not None
    assert profile.is_self is True


def test_full_composes_ledger_open_loops(tmp_path, monkeypatch):
    from solstone.think.surfaces import ledger as ledger_surface
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},),
    )
    _write_facet_relationship(tmp_path, "work", "ravi", description="Customer")
    _write_story_activity(
        "work",
        "20260418",
        "meeting_090000_300",
        _utc_ms("20260418", 9),
        commitments=[_commitment(counterparty="Ravi", counterparty_entity_id="ravi")],
    )

    expected = tuple(ledger_surface.list(state="open", counterparty="ravi"))
    profile = profile_surface.full("Ravi")

    assert profile is not None
    assert profile.open_with_them == expected


def test_full_composes_ledger_closed_30d(tmp_path, monkeypatch):
    from solstone.think.surfaces import ledger as ledger_surface
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},),
    )
    _write_facet_relationship(tmp_path, "work", "ravi", description="Customer")
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")
    _write_story_activity(
        "work",
        "20260410",
        "meeting_090000_300",
        _utc_ms("20260410", 9),
        commitments=[_commitment(counterparty="Ravi", counterparty_entity_id="ravi")],
    )
    _write_story_activity(
        "work",
        "20260415",
        "meeting_100000_300",
        _utc_ms("20260415", 10),
        closures=[
            {
                "owner": "Mina",
                "owner_entity_id": "mina",
                "action": "send proposal",
                "counterparty": "Ravi",
                "counterparty_entity_id": "ravi",
                "resolution": "sent",
                "context": "Closure context.",
            }
        ],
    )
    _write_story_activity(
        "work",
        "20260301",
        "meeting_080000_300",
        _utc_ms("20260301", 8),
        commitments=[
            _commitment(
                action="archive contract",
                counterparty="Ravi",
                counterparty_entity_id="ravi",
            )
        ],
    )
    _write_story_activity(
        "work",
        "20260305",
        "meeting_083000_300",
        _utc_ms("20260305", 8),
        closures=[
            {
                "owner": "Mina",
                "owner_entity_id": "mina",
                "action": "archive contract",
                "counterparty": "Ravi",
                "counterparty_entity_id": "ravi",
                "resolution": "archived",
                "context": "Old closure context.",
            }
        ],
    )

    expected = tuple(
        ledger_surface.list(
            state="closed",
            closed_since=profile_surface._day_minus(30),  # noqa: SLF001
            counterparty="ravi",
        )
    )
    profile = profile_surface.full("Ravi")

    assert profile is not None
    assert profile.closed_with_them_30d == expected
    assert len(profile.closed_with_them_30d) >= 1
    assert all(
        item.action != "archive contract" for item in profile.closed_with_them_30d
    )


def test_full_composes_ledger_decisions(tmp_path, monkeypatch):
    from solstone.think.surfaces import ledger as ledger_surface
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},),
    )
    _write_facet_relationship(tmp_path, "work", "ravi", description="Customer")
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")
    _write_story_activity(
        "work",
        "20260419",
        "meeting_090000_300",
        _utc_ms("20260419", 9),
        decisions=[_decision(owner="Ravi", owner_entity_id="ravi")],
    )

    expected = tuple(ledger_surface.decisions(involving="ravi"))
    profile = profile_surface.full("Ravi")

    assert profile is not None
    assert profile.decisions_involving_them == expected
    assert len(profile.decisions_involving_them) >= 1


def test_full_decisions_involving_them_includes_old(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},),
    )
    _write_facet_relationship(tmp_path, "work", "ravi", description="Customer")
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")
    _write_story_activity(
        "work",
        "20260301",
        "meeting_090000_300",
        _utc_ms("20260301", 9),
        decisions=[_decision(owner="Ravi", owner_entity_id="ravi")],
    )

    profile = profile_surface.full("Ravi")

    assert profile is not None
    assert any(
        decision.day == "20260301" for decision in profile.decisions_involving_them
    )


def test_not_found_returns_none(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(tmp_path)

    assert profile_surface.full("missing") is None


def test_brief_shape_and_counts(tmp_path, monkeypatch):
    from solstone.think.surfaces import ledger as ledger_surface
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},),
    )
    _write_facet_relationship(tmp_path, "work", "ravi", description="Customer")
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")
    _write_story_activity(
        "work",
        "20260418",
        "meeting_090000_300",
        _utc_ms("20260418", 9),
        commitments=[_commitment(counterparty="Ravi", counterparty_entity_id="ravi")],
    )
    _write_story_activity(
        "work",
        "20260419",
        "meeting_100000_300",
        _utc_ms("20260419", 10),
        decisions=[_decision(owner="Ravi", owner_entity_id="ravi")],
    )

    brief = profile_surface.brief("Ravi")

    assert brief is not None
    assert tuple(brief.__dataclass_fields__) == (
        "entity_id",
        "name",
        "type",
        "description",
        "last_seen",
        "open_loop_count",
        "decisions_count_30d",
    )
    assert brief.entity_id == "ravi"
    assert brief.name == "Ravi"
    assert brief.type == "Person"
    assert brief.description == "Customer"
    assert brief.last_seen is None
    assert brief.open_loop_count == len(
        ledger_surface.list(state="open", counterparty="ravi")
    )
    assert brief.decisions_count_30d == len(
        ledger_surface.decisions(involving="ravi", since="20260321")
    )
    assert not hasattr(brief, "is_self")
    assert not hasattr(brief, "generated_at")


def test_list_active_sort_dedup_window(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(tmp_path, facets=("personal", "work"))
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")

    _append_activity(
        "work",
        "20260418",
        _activity_record(
            "20260418",
            [
                _participant("zoe", name="Zoe"),
                _participant("anna", name="Anna"),
                _participant(None, name="Unknown"),
            ],
            record_id="meeting_work",
        ),
    )
    _append_activity(
        "personal",
        "20260419",
        _activity_record(
            "20260419",
            [_participant("anna", name="Anna")],
            record_id="meeting_personal",
        ),
    )
    _append_activity(
        "work",
        "20260301",
        _activity_record(
            "20260301",
            [_participant("outside_window", name="Outside")],
            record_id="meeting_old",
        ),
    )

    assert profile_surface.list_active(window_days=30) == ["anna", "zoe"]


def test_list_active_excludes_mentioned(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(tmp_path)
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")
    _append_activity(
        "work",
        "20260418",
        _activity_record(
            "20260418",
            [_participant("ravi", name="Ravi", role="mentioned")],
            record_id="meeting_mentioned",
        ),
    )

    assert profile_surface.list_active(window_days=30) == []


def test_muted_facet_included_in_cadence(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)
    _minimal_facet_tree(
        tmp_path,
        facets=("quiet",),
        muted_facets=("quiet",),
        journal_entities=({"id": "ravi", "name": "Ravi", "type": "Person"},),
    )
    _write_facet_relationship(tmp_path, "quiet", "ravi", description="Muted facet")
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")
    _append_activity(
        "quiet",
        "20260418",
        _activity_record(
            "20260418",
            [_participant("ravi", name="Ravi")],
            record_id="meeting_quiet",
        ),
    )

    cadence = profile_surface.cadence("Ravi")
    assert cadence == Cadence(1, "20260418", None, None)


def test_utc_day_math(tmp_path, monkeypatch):
    from solstone.think.surfaces import profile as profile_surface

    _configure_env(tmp_path, monkeypatch)

    assert re.fullmatch(r"\d{8}", profile_surface._today_day()) is not None  # noqa: SLF001
    monkeypatch.setattr(profile_surface, "_today_day", lambda: "20260420")
    assert profile_surface._day_minus(30) == "20260321"  # noqa: SLF001
