"""Session binding helpers for the current fingerprint context."""

from __future__ import annotations

import json
import os
from pathlib import Path

from gotta.content import (
    CONTENT_ENV,
    SESSION_CREATED_ENV,
    SESSION_ACTOR_ENV,
    SESSION_ID_ENV,
    CommonOptions,
    current_context_binding,
    ensure_private_dir,
    iso_utc,
    load_state_env_at_root,
    resolve_dirs,
    resolve_session_reference,
    session_actor_scope,
    session_id,
    session_token,
    session_surface_initialized,
    write_text_atomic,
    write_session_state,
)
from gotta import session as session_plugin
from gotta import topology


def _ensure_link(path: Path, target: Path) -> None:
    desired = os.path.relpath(target, start=path.parent)
    if path.is_symlink():
        current = os.readlink(path)
        if current == desired:
            return
        path.unlink()
    elif path.exists():
        if path.is_file():
            path.unlink()
        else:
            raise SystemExit(f"refusing to replace existing directory: {path}")
    ensure_private_dir(path.parent)
    path.symlink_to(desired)


def _update_session_metadata(session_dir: Path, *, session_id: str, actor: str) -> None:
    metadata_path = session_dir / "session.json"
    payload: dict[str, object]
    if metadata_path.exists():
        try:
            existing = json.loads(metadata_path.read_text(encoding="utf-8"))
        except json.JSONDecodeError:
            existing = {}
        payload = existing if isinstance(existing, dict) else {}
    else:
        payload = {}
    members = payload.get("members")
    if not isinstance(members, list):
        members = []
    normalized_members = [
        topology.normalize_identity(str(item)) for item in members if str(item).strip()
    ]
    if actor not in normalized_members:
        normalized_members.append(actor)
    payload["session_id"] = session_id
    payload.setdefault("created_at", iso_utc())
    payload["updated_at"] = iso_utc()
    payload["members"] = sorted(dict.fromkeys(normalized_members))
    actors = payload.get("actors")
    if not isinstance(actors, dict):
        actors = {}
    actors.setdefault(
        actor,
        {
            "label": actor,
            "model": session_plugin.ACTOR_DEFAULT_MODEL,
            "resume_uuid": "",
            "template": "",
        },
    )
    payload["actors"] = actors
    write_text_atomic(
        metadata_path,
        json.dumps(payload, indent=2, sort_keys=True) + "\n",
    )


def ensure_actor_session(
    root: Path,
    *,
    context_id: str,
    context_source: str,
) -> tuple[Path, bool]:
    resolved = root.expanduser().resolve()
    current_session_id = session_id(resolved)
    actor = session_actor_scope(resolved)
    actor_branch = (
        resolved.parent.name == "actors"
        or topology.parse_grouped_session_root(resolved) is not None
    )
    session_group_dir = session_plugin._group_session_root(resolved)
    content_dir = session_group_dir / "content"
    ensure_private_dir(session_group_dir)
    ensure_private_dir(content_dir)
    dirs = resolve_dirs(
        CommonOptions(
            session_dir=str(resolved),
            content_dir=str(content_dir),
            actor=actor,
        ),
        create=True,
    )
    state = load_state_env_at_root(dirs.session_dir)
    write_session_state(
        dirs,
        {
            SESSION_CREATED_ENV: str(state.get(SESSION_CREATED_ENV) or "") or iso_utc(),
            SESSION_ID_ENV: current_session_id,
            SESSION_ACTOR_ENV: actor,
        },
    )
    content_link = dirs.session_dir / "content"
    if content_link.resolve() != content_dir.resolve():
        _ensure_link(content_link, content_dir)
    session_link = dirs.session_dir / "session"
    if session_link.is_symlink() or session_link.is_file():
        session_link.unlink(missing_ok=True)
    if actor_branch and actor:
        _update_session_metadata(
            session_group_dir,
            session_id=current_session_id,
            actor=actor,
        )
    created = not session_surface_initialized(dirs.session_dir)
    if created:
        session_plugin.scaffold_session(dirs.session_dir)
    return dirs.session_dir.resolve(), created


def _resolve_bind_root(session_ref: str | None, *, binding_id: str) -> Path:
    if not (session_ref or "").strip():
        return topology.session_root_for(binding_id, binding_id).resolve()
    resolved = resolve_session_reference(
        session_ref or "",
        identity=binding_id,
        allow_missing=True,
    )
    if resolved is None:
        raise SystemExit(
            "session references for `gotta session bind` must be an absolute path, "
            "a shared session id, or an explicit <session>/<actor> session reference"
        )
    return resolved.resolve()


def bind_root_for_context(
    root: Path,
    *,
    context_id: str,
    context_source: str,
) -> Path:
    binding_id = topology.normalize_identity(session_id_from_context(context_id))
    root, _created = ensure_actor_session(
        root,
        context_id=context_id,
        context_source=context_source,
    )
    now = iso_utc()
    existing = topology.load_binding_record(binding_id) or {}
    topology.write_binding(
        binding_id,
        root,
        context_id=context_id,
        context_source=context_source,
        session_id=session_id(root),
        actor=session_actor_scope(root),
        created_at=str(existing.get("createdAt") or now),
        updated_at=now,
    )
    return root


def _resolved_payload(root: Path) -> dict[str, str]:
    resolved = root.resolve()
    state = load_state_env_at_root(resolved)
    return {
        "sessionRoot": str(resolved),
        "sessionId": session_id(resolved),
        "sessionDir": str(session_plugin._group_session_root(resolved)),
        "actor": session_actor_scope(resolved),
        "content": str(state.get(CONTENT_ENV) or (resolved / "content")),
    }


def print_payload(payload: dict[str, str], *, output: str) -> int:
    if output == "json":
        print(json.dumps(payload, indent=2, sort_keys=True))
        return 0
    if output == "path":
        print(payload["sessionRoot"])
        return 0
    print(f"session\t{payload['sessionId']}")
    print(f"actor\t{payload['actor']}")
    print(f"root\t{payload['sessionRoot']}")
    print(f"content\t{payload['content']}")
    return 0


def bind_current_context(
    *,
    session_ref: str | None,
    output: str,
) -> int:
    context = current_context_binding()
    context_id = context.context_id
    context_source = context.context_source
    binding_id = topology.normalize_identity(session_id_from_context(context_id))
    root = _resolve_bind_root(session_ref, binding_id=binding_id)
    root = bind_root_for_context(
        root,
        context_id=context_id,
        context_source=context_source,
    )
    return print_payload(_resolved_payload(root), output=output)


def session_id_from_context(context_id: str) -> str:
    return session_token(context_id)
