"""Append-only decision log (ADR store) -- JSONL with thread lock."""

import json
import logging
import secrets
import threading
from dataclasses import dataclass, field
from datetime import datetime, timezone
from pathlib import Path

_log = logging.getLogger("swarm_kb.decision_store")


@dataclass
class Decision:
    """An architectural decision record."""

    id: str = ""
    created_at: str = ""
    title: str = ""
    status: str = "proposed"  # proposed|accepted|rejected|superseded
    rationale: str = ""
    context: str = ""
    consequences: list[str] = field(default_factory=list)
    source_tool: str = ""  # arch|review|fix|manual
    source_session: str = ""
    debate_id: str = ""
    project_path: str = ""
    tags: list[str] = field(default_factory=list)
    superseded_by: str = ""

    @staticmethod
    def generate_id() -> str:
        return "adr-" + secrets.token_hex(4)

    def to_dict(self) -> dict:
        return {
            "id": self.id,
            "created_at": self.created_at,
            "title": self.title,
            "status": self.status,
            "rationale": self.rationale,
            "context": self.context,
            "consequences": self.consequences,
            "source_tool": self.source_tool,
            "source_session": self.source_session,
            "debate_id": self.debate_id,
            "project_path": self.project_path,
            "tags": self.tags,
            "superseded_by": self.superseded_by,
        }

    @classmethod
    def from_dict(cls, d: dict) -> "Decision":
        return cls(
            id=d.get("id", Decision.generate_id()),
            created_at=d.get("created_at", ""),
            title=d.get("title", ""),
            status=d.get("status", "proposed"),
            rationale=d.get("rationale", ""),
            context=d.get("context", ""),
            consequences=d.get("consequences", []),
            source_tool=d.get("source_tool", ""),
            source_session=d.get("source_session", ""),
            debate_id=d.get("debate_id", ""),
            project_path=d.get("project_path", ""),
            tags=d.get("tags", []),
            superseded_by=d.get("superseded_by", ""),
        )


class DecisionStore:
    """Append-only decision store with in-memory query support."""

    def __init__(self, path: Path) -> None:
        self._path = Path(path)
        self._entries: list[Decision] = []
        self._lock = threading.Lock()
        self._load()

    def _load(self) -> None:
        """Load existing entries from disk."""
        if not self._path.exists():
            return
        with self._lock:
            for line in self._path.read_text(encoding="utf-8").splitlines():
                line = line.strip()
                if not line:
                    continue
                try:
                    self._entries.append(Decision.from_dict(json.loads(line)))
                except Exception as exc:
                    _log.warning("Skipping corrupt line in %s: %s", self._path, exc)

    def append(self, **kwargs: object) -> Decision:
        """Create and persist a new decision."""
        decision = Decision(
            id=Decision.generate_id(),
            created_at=datetime.now(timezone.utc).isoformat(),
            **kwargs,  # type: ignore[arg-type]
        )

        with self._lock:
            self._path.parent.mkdir(parents=True, exist_ok=True)
            with open(self._path, "a", encoding="utf-8") as fh:
                fh.write(json.dumps(decision.to_dict()) + "\n")
            self._entries.append(decision)

        _log.info("Decision %s: %s (%s)", decision.id, decision.title, decision.status)
        return decision

    def query(
        self,
        *,
        status: str = "",
        source_tool: str = "",
        tag: str = "",
        project_path: str = "",
    ) -> list[Decision]:
        """Query decisions with optional filters."""
        with self._lock:
            results = list(self._entries)

        if status:
            results = [d for d in results if d.status == status]
        if source_tool:
            results = [d for d in results if d.source_tool == source_tool]
        if tag:
            results = [d for d in results if tag in d.tags]
        if project_path:
            results = [d for d in results if d.project_path == project_path]

        return results

    def get_by_id(self, decision_id: str) -> Decision | None:
        """Get a specific decision by ID."""
        with self._lock:
            for d in self._entries:
                if d.id == decision_id:
                    return d
        return None

    def update_status(
        self, decision_id: str, new_status: str, superseded_by: str = ""
    ) -> bool:
        """Update a decision's status. Rewrites the JSONL file atomically."""
        with self._lock:
            found = False
            for d in self._entries:
                if d.id == decision_id:
                    d.status = new_status
                    if superseded_by:
                        d.superseded_by = superseded_by
                    found = True
                    break

            if not found:
                return False

            # Rewrite file with updated entries
            self._path.parent.mkdir(parents=True, exist_ok=True)
            lines = [json.dumps(d.to_dict()) for d in self._entries]
            self._path.write_text("\n".join(lines) + "\n", encoding="utf-8")

        _log.info("Decision %s status -> %s", decision_id, new_status)
        return True

    def count(self) -> int:
        """Count total decisions."""
        with self._lock:
            return len(self._entries)
