"""File storage layer for context data."""

import re
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Optional

import yaml

from .schema import (
    Context,
    Note,
    Reference,
    Step,
    StepStatus,
    Task,
    TaskStatus,
)

# Archive threshold for score
ARCHIVE_SCORE_THRESHOLD = -5

# Archive threshold for done tasks (days)
ARCHIVE_DONE_DAYS = 7

# Max done tasks before archiving oldest
MAX_DONE_TASKS = 10


class ContextStorage:
    """Handles reading and writing context data to YAML files."""

    def __init__(self, root: Optional[Path] = None):
        """Initialize storage with project root directory.
        
        Args:
            root: Project root directory. Defaults to current working directory.
        """
        self.root = Path(root) if root else Path.cwd()
        self.context_dir = self.root / "context"
        self.tasks_dir = self.context_dir / "tasks"
        self.archive_dir = self.context_dir / "archive"
        self.archive_tasks_dir = self.archive_dir / "tasks"

    def _ensure_dirs(self) -> None:
        """Create context directories if they don't exist."""
        self.context_dir.mkdir(parents=True, exist_ok=True)
        self.tasks_dir.mkdir(parents=True, exist_ok=True)
        self.archive_dir.mkdir(parents=True, exist_ok=True)
        self.archive_tasks_dir.mkdir(parents=True, exist_ok=True)

    def _context_path(self) -> Path:
        """Get path to context.yaml."""
        return self.context_dir / "context.yaml"

    def _task_path(self, task_id: str) -> Path:
        """Get path to a task file."""
        return self.tasks_dir / f"{task_id}.yaml"

    def _archive_task_path(self, task_id: str) -> Path:
        """Get path to an archived task file."""
        return self.archive_tasks_dir / f"{task_id}.yaml"

    def _archive_notes_path(self) -> Path:
        """Get path to archived notes file."""
        return self.archive_dir / "notes.yaml"

    def _archive_refs_path(self) -> Path:
        """Get path to archived references file."""
        return self.archive_dir / "references.yaml"

    # === Context Operations ===

    def load_context(self) -> Context:
        """Load context from context.yaml.
        
        Returns empty Context if file doesn't exist.
        """
        path = self._context_path()
        if not path.exists():
            return Context()
        
        try:
            data = yaml.safe_load(path.read_text(encoding="utf-8"))
            if data is None:
                return Context()
            
            # Parse notes
            notes = []
            for n in data.get("notes", []):
                notes.append(Note(
                    id=n.get("id", ""),
                    score=n.get("score", 15),
                    content=n.get("content", "")
                ))
            
            # Parse references
            refs = []
            for r in data.get("references", []):
                refs.append(Reference(
                    id=r.get("id", ""),
                    score=r.get("score", 15),
                    uri=r.get("uri", ""),
                    note=r.get("note", "")
                ))
            
            return Context(
                version=data.get("version", "1"),
                goal=data.get("goal"),
                why=data.get("why"),
                agents=data.get("agents", {}),
                notes=notes,
                references=refs
            )
        except yaml.YAMLError:
            return Context()

    def save_context(self, context: Context) -> None:
        """Save context to context.yaml."""
        self._ensure_dirs()
        
        # Convert to dict for YAML
        data = {
            "version": context.version,
            "goal": context.goal,
            "why": context.why,
            "agents": context.agents,
            "notes": [
                {"id": n.id, "score": n.score, "content": n.content}
                for n in context.notes
            ],
            "references": [
                {"id": r.id, "score": r.score, "uri": r.uri, "note": r.note}
                for r in context.references
            ]
        }
        
        path = self._context_path()
        path.write_text(
            yaml.safe_dump(data, allow_unicode=True, sort_keys=False, default_flow_style=False),
            encoding="utf-8"
        )

    # === Task Operations ===

    def load_task(self, task_id: str, include_archived: bool = False) -> Optional[Task]:
        """Load a task by ID.
        
        Args:
            task_id: Task ID (e.g., T001)
            include_archived: Whether to search archived tasks
            
        Returns:
            Task if found, None otherwise.
        """
        path = self._task_path(task_id)
        if path.exists():
            return self._parse_task(path)
        
        if include_archived:
            archive_path = self._archive_task_path(task_id)
            if archive_path.exists():
                return self._parse_task(archive_path)
        
        return None

    def _parse_task(self, path: Path) -> Optional[Task]:
        """Parse a task from a YAML file."""
        try:
            data = yaml.safe_load(path.read_text(encoding="utf-8"))
            if data is None:
                return None
            
            steps = []
            for s in data.get("steps", []):
                steps.append(Step(
                    id=s.get("id", ""),
                    name=s.get("name", ""),
                    done=s.get("done", ""),
                    status=StepStatus(s.get("status", "pending"))
                ))
            
            return Task(
                id=data.get("id", ""),
                name=data.get("name", ""),
                goal=data.get("goal", ""),
                status=TaskStatus(data.get("status", "planned")),
                assignee=data.get("assignee"),
                created_at=data.get("created_at", ""),
                steps=steps
            )
        except (yaml.YAMLError, ValueError):
            return None

    def save_task(self, task: Task) -> None:
        """Save a task to its file."""
        self._ensure_dirs()
        
        data = {
            "id": task.id,
            "name": task.name,
            "goal": task.goal,
            "status": task.status.value,
            "assignee": task.assignee,
            "created_at": task.created_at,
            "steps": [
                {"id": s.id, "name": s.name, "done": s.done, "status": s.status.value}
                for s in task.steps
            ]
        }
        
        path = self._task_path(task.id)
        path.write_text(
            yaml.safe_dump(data, allow_unicode=True, sort_keys=False, default_flow_style=False),
            encoding="utf-8"
        )

    def delete_task(self, task_id: str) -> bool:
        """Delete a task file.
        
        Returns:
            True if deleted, False if not found.
        """
        path = self._task_path(task_id)
        if path.exists():
            path.unlink()
            return True
        return False

    def list_tasks(self, include_archived: bool = False) -> list[Task]:
        """List all tasks.
        
        Args:
            include_archived: Whether to include archived tasks
            
        Returns:
            List of tasks sorted by ID.
        """
        tasks = []
        
        if self.tasks_dir.exists():
            for path in self.tasks_dir.glob("T*.yaml"):
                task = self._parse_task(path)
                if task:
                    tasks.append(task)
        
        if include_archived and self.archive_tasks_dir.exists():
            for path in self.archive_tasks_dir.glob("T*.yaml"):
                task = self._parse_task(path)
                if task:
                    tasks.append(task)
        
        # Sort by ID
        tasks.sort(key=lambda t: t.id)
        return tasks

    def generate_task_id(self) -> str:
        """Generate the next task ID.
        
        Scans existing tasks and returns the next available ID.
        """
        max_num = 0
        
        if self.tasks_dir.exists():
            for path in self.tasks_dir.glob("T*.yaml"):
                match = re.match(r"T(\d+)", path.stem)
                if match:
                    num = int(match.group(1))
                    max_num = max(max_num, num)
        
        if self.archive_tasks_dir.exists():
            for path in self.archive_tasks_dir.glob("T*.yaml"):
                match = re.match(r"T(\d+)", path.stem)
                if match:
                    num = int(match.group(1))
                    max_num = max(max_num, num)
        
        return f"T{max_num + 1:03d}"

    # === Entry Operations (Notes and References) ===

    def generate_note_id(self, context: Context) -> str:
        """Generate the next note ID."""
        max_num = 0
        for note in context.notes:
            match = re.match(r"N(\d+)", note.id)
            if match:
                num = int(match.group(1))
                max_num = max(max_num, num)
        return f"N{max_num + 1:03d}"

    def generate_reference_id(self, context: Context) -> str:
        """Generate the next reference ID."""
        max_num = 0
        for ref in context.references:
            match = re.match(r"R(\d+)", ref.id)
            if match:
                num = int(match.group(1))
                max_num = max(max_num, num)
        return f"R{max_num + 1:03d}"

    # === Archive Operations ===

    def decay_scores(self, context: Context) -> tuple[Context, list[Note], list[Reference]]:
        """Decay scores for all notes and references.
        
        Called during get_context(). Decrements all scores by 1.
        
        Args:
            context: Current context
            
        Returns:
            Tuple of (updated context, archived notes, archived references)
        """
        archived_notes = []
        archived_refs = []
        remaining_notes = []
        remaining_refs = []
        
        for note in context.notes:
            note.score -= 1
            if note.score <= ARCHIVE_SCORE_THRESHOLD:
                archived_notes.append(note)
            else:
                remaining_notes.append(note)
        
        for ref in context.references:
            ref.score -= 1
            if ref.score <= ARCHIVE_SCORE_THRESHOLD:
                archived_refs.append(ref)
            else:
                remaining_refs.append(ref)
        
        context.notes = remaining_notes
        context.references = remaining_refs
        
        return context, archived_notes, archived_refs

    def archive_entries(self, notes: list[Note], refs: list[Reference]) -> None:
        """Archive notes and references to archive files."""
        if not notes and not refs:
            return
        
        self._ensure_dirs()
        
        # Archive notes
        if notes:
            existing = []
            notes_path = self._archive_notes_path()
            if notes_path.exists():
                try:
                    data = yaml.safe_load(notes_path.read_text(encoding="utf-8"))
                    existing = data if isinstance(data, list) else []
                except yaml.YAMLError:
                    existing = []
            
            for note in notes:
                existing.append({
                    "id": note.id,
                    "score": note.score,
                    "content": note.content,
                    "archived_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
                })
            
            notes_path.write_text(
                yaml.safe_dump(existing, allow_unicode=True, sort_keys=False),
                encoding="utf-8"
            )
        
        # Archive references
        if refs:
            existing = []
            refs_path = self._archive_refs_path()
            if refs_path.exists():
                try:
                    data = yaml.safe_load(refs_path.read_text(encoding="utf-8"))
                    existing = data if isinstance(data, list) else []
                except yaml.YAMLError:
                    existing = []
            
            for ref in refs:
                existing.append({
                    "id": ref.id,
                    "score": ref.score,
                    "uri": ref.uri,
                    "note": ref.note,
                    "archived_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
                })
            
            refs_path.write_text(
                yaml.safe_dump(existing, allow_unicode=True, sort_keys=False),
                encoding="utf-8"
            )

    def archive_old_tasks(self) -> list[str]:
        """Archive old done tasks.
        
        Archives tasks that are:
        - Done for more than ARCHIVE_DONE_DAYS days
        - Or if there are more than MAX_DONE_TASKS done tasks (archives oldest)
        
        Returns:
            List of archived task IDs.
        """
        archived_ids = []
        tasks = self.list_tasks(include_archived=False)
        
        done_tasks = [t for t in tasks if t.status == TaskStatus.DONE]
        
        if not done_tasks:
            return archived_ids
        
        # Check by date
        now = datetime.now(timezone.utc)
        threshold = now - timedelta(days=ARCHIVE_DONE_DAYS)
        
        for task in done_tasks:
            try:
                created = datetime.fromisoformat(task.created_at.rstrip("Z"))
                if created < threshold:
                    self._move_to_archive(task.id)
                    archived_ids.append(task.id)
            except ValueError:
                pass
        
        # Check by count (archive oldest if too many)
        remaining_done = [t for t in done_tasks if t.id not in archived_ids]
        if len(remaining_done) > MAX_DONE_TASKS:
            # Sort by created_at and archive oldest
            remaining_done.sort(key=lambda t: t.created_at)
            to_archive = remaining_done[:len(remaining_done) - MAX_DONE_TASKS]
            for task in to_archive:
                self._move_to_archive(task.id)
                archived_ids.append(task.id)
        
        return archived_ids

    def _move_to_archive(self, task_id: str) -> None:
        """Move a task file to archive."""
        src = self._task_path(task_id)
        dst = self._archive_task_path(task_id)
        
        if src.exists():
            self._ensure_dirs()
            dst.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
            src.unlink()
