# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Review queue for Smart Intake — human-in-the-loop review of low-confidence classifications.

Thread-safe JSON store following the IntakeHistory pattern.
Items are flagged for review when confidence < 0.5 or type == UNKNOWN.
"""

import json
import logging
import os
import secrets
import threading
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional

logger = logging.getLogger(__name__)

_lock = threading.Lock()


def _data_dir() -> Path:
    try:
        from familiar.core.paths import DATA_DIR

        return DATA_DIR
    except ImportError:
        return Path.home() / ".familiar" / "data"


# Thresholds for auto-flagging
REVIEW_CONFIDENCE_THRESHOLD = 0.5
MAX_QUEUE_SIZE = 500


class ReviewQueue:
    """Thread-safe JSON store for intake review queue."""

    def __init__(self, data_file: Optional[Path] = None):
        self._file = data_file or (_data_dir() / "scribe_queue.json")
        self._ensure_file()

    def _ensure_file(self):
        if not self._file.exists():
            self._file.parent.mkdir(parents=True, exist_ok=True)
            self._write({"items": [], "processed": [], "version": 1})

    def _read(self) -> dict:
        try:
            return json.loads(self._file.read_text(encoding="utf-8"))
        except Exception:
            return {"items": [], "processed": [], "version": 1}

    def _write(self, data: dict):
        tmp = self._file.with_suffix(".tmp")
        tmp.write_text(json.dumps(data, indent=2, default=str), encoding="utf-8")
        os.replace(str(tmp), str(self._file))
        try:
            os.chmod(str(self._file), 0o600)
        except OSError:
            pass

    def needs_review(self, confidence: float, content_type: str) -> bool:
        """Check if a classification result should be flagged for review."""
        return confidence < REVIEW_CONFIDENCE_THRESHOLD or content_type == "unknown"

    def add_for_review(
        self,
        source_file: str,
        suggested_type: str,
        confidence: float,
        extracted_text: str,
        extracted_fields: Optional[dict] = None,
        method: str = "keyword",
    ) -> str:
        """Add an item to the review queue. Returns item ID."""
        item_id = f"review_{secrets.token_hex(6)}"
        item = {
            "id": item_id,
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "source_file": source_file,
            "suggested_type": suggested_type,
            "confidence": confidence,
            "text_preview": (extracted_text or "")[:500],
            "extracted_fields": extracted_fields or {},
            "method": method,
            "status": "pending",
        }

        with _lock:
            data = self._read()
            data["items"].insert(0, item)
            # Cap queue size
            data["items"] = data["items"][:MAX_QUEUE_SIZE]
            self._write(data)

        return item_id

    def list_pending(self, limit: int = 50) -> list[dict]:
        """List items awaiting review."""
        data = self._read()
        pending = [i for i in data.get("items", []) if i.get("status") == "pending"]
        return pending[:limit]

    def get_item(self, item_id: str) -> Optional[dict]:
        """Get a specific review item by ID."""
        data = self._read()
        for item in data.get("items", []):
            if item.get("id") == item_id:
                return item
        return None

    def approve(
        self,
        item_id: str,
        corrected_type: Optional[str] = None,
        corrected_fields: Optional[dict] = None,
    ) -> Optional[dict]:
        """Approve a review item, optionally with corrections. Returns the item."""
        with _lock:
            data = self._read()
            for item in data.get("items", []):
                if item.get("id") == item_id and item.get("status") == "pending":
                    item["status"] = "approved"
                    item["reviewed_at"] = datetime.now(timezone.utc).isoformat()
                    if corrected_type:
                        item["corrected_type"] = corrected_type
                    if corrected_fields:
                        item["corrected_fields"] = corrected_fields
                    # Move to processed list
                    data.setdefault("processed", []).insert(0, item.copy())
                    data["processed"] = data["processed"][:1000]
                    self._write(data)
                    return item
        return None

    def reject(self, item_id: str, reason: str = "") -> Optional[dict]:
        """Reject a review item. Returns the item."""
        with _lock:
            data = self._read()
            for item in data.get("items", []):
                if item.get("id") == item_id and item.get("status") == "pending":
                    item["status"] = "rejected"
                    item["reviewed_at"] = datetime.now(timezone.utc).isoformat()
                    item["reject_reason"] = reason
                    data.setdefault("processed", []).insert(0, item.copy())
                    data["processed"] = data["processed"][:1000]
                    self._write(data)
                    return item
        return None

    def get_stats(self) -> dict:
        """Get review queue statistics."""
        data = self._read()
        items = data.get("items", [])
        processed = data.get("processed", [])

        pending = sum(1 for i in items if i.get("status") == "pending")
        approved = sum(1 for i in processed if i.get("status") == "approved")
        rejected = sum(1 for i in processed if i.get("status") == "rejected")
        corrected = sum(1 for i in processed if i.get("corrected_type"))
        total_reviewed = approved + rejected

        return {
            "pending": pending,
            "approved": approved,
            "rejected": rejected,
            "corrected": corrected,
            "total_reviewed": total_reviewed,
            "accuracy_pct": round((approved - corrected) / total_reviewed * 100, 1)
            if total_reviewed > 0
            else 100.0,
        }

    def clear_pending(self):
        """Clear all pending items."""
        with _lock:
            data = self._read()
            data["items"] = [i for i in data.get("items", []) if i.get("status") != "pending"]
            self._write(data)


def process_review_decision(
    item_id: str,
    action: str,
    corrected_type: Optional[str] = None,
    corrected_fields: Optional[dict] = None,
    reason: str = "",
) -> str:
    """Process a review decision — approve with optional corrections, or reject.

    If approved with corrections, re-routes the item through the intake pipeline
    with the corrected type and offers to learn a template.
    """
    queue = get_review_queue()
    item = queue.get_item(item_id)
    if not item:
        return f"Review item {item_id} not found."
    if item.get("status") != "pending":
        return f"Review item {item_id} already processed ({item.get('status')})."

    if action == "approve":
        result = queue.approve(
            item_id, corrected_type=corrected_type, corrected_fields=corrected_fields
        )
        if not result:
            return f"Failed to approve {item_id}."

        final_type = corrected_type or item.get("suggested_type", "unknown")

        # Re-route with corrected type if corrections were made
        if corrected_type and corrected_type != item.get("suggested_type"):
            try:
                from familiar.skills.intake.skill import smart_intake

                smart_intake(
                    {
                        "filepath": item.get("source_file", ""),
                        "content_type": final_type,
                        "skip_llm": True,
                    }
                )
            except Exception as e:
                logger.warning("Re-route after correction failed: %s", e)

            # Offer to learn template from correction
            try:
                from familiar.skills.intake.templates import get_template_store

                store = get_template_store()
                text = item.get("text_preview", "")
                fields = corrected_fields or item.get("extracted_fields", {})
                if text and fields:
                    store.learn_from_correction(
                        content_type=final_type,
                        text=text,
                        fields=fields,
                    )
            except Exception as e:
                logger.debug("Template learning failed: %s", e)

        return f"Approved: {item.get('source_file', 'unknown')} as {final_type}" + (
            f" (corrected from {item.get('suggested_type')})" if corrected_type else ""
        )

    elif action == "reject":
        result = queue.reject(item_id, reason=reason)
        if not result:
            return f"Failed to reject {item_id}."
        return f"Rejected: {item.get('source_file', 'unknown')} — {reason or 'no reason given'}"

    return f"Unknown action: {action}. Use 'approve' or 'reject'."


_instance: Optional[ReviewQueue] = None


def get_review_queue() -> ReviewQueue:
    """Singleton accessor."""
    global _instance
    if _instance is None:
        _instance = ReviewQueue()
    return _instance
