"""UserContext — scopes all agent resources to a specific user.

The single most important design for Phase 2 readiness.
Every runtime function takes a UserContext.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path


@dataclass(frozen=True)
class UserContext:
    """Scopes all agent resources to a specific user.

    Phase 1: Single user, current directory.
    Phase 2: Per-Linux-user scoping via PAM auth.
    """

    username: str
    home_dir: Path
    working_dir: Path
    config_dir: Path
    memory_sources: list[str] = field(default_factory=list)
    skill_sources: list[str] = field(default_factory=list)

    @property
    def memory_dir(self) -> Path:
        """Persistent memory directory, scoped per user.

        Phase 1: ~/.icarus/memory/
        Phase 2: /home/{username}/.icarus/memory/
        """
        return self.config_dir / "memory"

    @property
    def contacts_dir(self) -> Path:
        """Contacts directory (vCard .vcf files)."""
        return self.config_dir / "contacts"

    @property
    def calendar_dir(self) -> Path:
        """Calendar directory (iCalendar .ics VEVENT files)."""
        return self.config_dir / "calendar"

    @property
    def todos_dir(self) -> Path:
        """Todos directory (iCalendar .ics VTODO files)."""
        return self.config_dir / "todos"

    @classmethod
    def default(cls) -> UserContext:
        """Phase 1: single user, current directory.

        Uses cwd as working directory and ~/.icarus as config directory.
        """
        cwd = Path.cwd()
        home = Path.home()
        config_dir = home / ".icarus"

        # Memory sources: user-level + project-level AGENTS.md
        memory_sources = []
        user_agents_md = config_dir / "AGENTS.md"
        if user_agents_md.exists():
            memory_sources.append(str(user_agents_md))
        # Project-level AGENTS.md (in cwd)
        project_agents_md = cwd / "AGENTS.md"
        if project_agents_md.exists():
            memory_sources.append(str(project_agents_md))

        # Skill sources: built-in → user → project (ascending priority)
        skill_sources = cls._build_skill_sources(config_dir, cwd)

        return cls(
            username="default",
            home_dir=home,
            working_dir=cwd,
            config_dir=config_dir,
            memory_sources=memory_sources,
            skill_sources=skill_sources,
        )

    @classmethod
    def with_workspace(cls, working_dir: Path) -> UserContext:
        """Like ``default()`` but scoped to an explicit working directory.

        Used by the desktop sidecar so users can point the agent at a
        specific folder without relying on ``cwd``.

        Args:
            working_dir: Absolute path to use as the agent's working directory.

        Returns:
            UserContext scoped to *working_dir* with the default home/config dirs.
        """
        home = Path.home()
        config_dir = home / ".icarus"

        memory_sources = []
        user_agents_md = config_dir / "AGENTS.md"
        if user_agents_md.exists():
            memory_sources.append(str(user_agents_md))
        project_agents_md = working_dir / "AGENTS.md"
        if project_agents_md.exists():
            memory_sources.append(str(project_agents_md))

        skill_sources = cls._build_skill_sources(config_dir, working_dir)

        return cls(
            username="default",
            home_dir=home,
            working_dir=working_dir,
            config_dir=config_dir,
            memory_sources=memory_sources,
            skill_sources=skill_sources,
        )

    @classmethod
    def for_user(cls, username: str) -> UserContext:
        """Phase 2: scoped to a Linux user's home directory.

        Args:
            username: Linux username (from PAM auth).

        Returns:
            UserContext scoped to that user's directories.
        """
        home_dir = Path(f"/home/{username}")
        config_dir = home_dir / ".icarus"
        working_dir = home_dir / "workspace"

        # Memory sources for this user
        memory_sources = []
        user_agents_md = config_dir / "AGENTS.md"
        if user_agents_md.exists():
            memory_sources.append(str(user_agents_md))

        # Skill sources with shared layer
        skill_sources = cls._build_skill_sources(
            config_dir, working_dir, shared_dir=Path("/nas/shared/skills")
        )

        return cls(
            username=username,
            home_dir=home_dir,
            working_dir=working_dir,
            config_dir=config_dir,
            memory_sources=memory_sources,
            skill_sources=skill_sources,
        )

    @classmethod
    def for_serve_user(cls, user_id: str, server_dir: Path) -> UserContext:
        """Create context for an icarus-serve user.

        All paths scoped to server_dir/users/{user_id}/.
        Unlike for_user() (Linux home dirs), this uses the server's
        managed user directory.
        """
        user_dir = server_dir / "users" / user_id
        return cls(
            username=user_id,
            home_dir=user_dir,
            working_dir=user_dir,
            config_dir=user_dir,
            memory_sources=[
                str(p) for p in [user_dir / "AGENTS.md"]
                if p.exists()
            ],
            skill_sources=cls._build_skill_sources(
                user_dir, user_dir, shared_dir=server_dir / "skills",
            ),
        )

    @staticmethod
    def _build_skill_sources(
        config_dir: Path,
        working_dir: Path,
        *,
        shared_dir: Path | None = None,
    ) -> list[str]:
        """Build layered skill sources (lowest → highest priority).

        Args:
            config_dir: User config directory (~/.icarus).
            working_dir: Current working/project directory.
            shared_dir: Optional shared skills directory (Phase 2).

        Returns:
            List of skill source directory paths.
        """
        # Get the built-in skills directory (inside the package)
        builtin_dir = Path(__file__).parent.parent / "skills"
        sources = [str(builtin_dir)]

        if shared_dir and shared_dir.exists():
            sources.append(str(shared_dir))

        # User-level skills
        user_skills = config_dir / "skills"
        sources.append(str(user_skills))

        # Project-level skills
        project_skills = working_dir / ".icarus" / "skills"
        sources.append(str(project_skills))

        return sources

    def ensure_dirs(self) -> None:
        """Create necessary directories if they don't exist."""
        self.config_dir.mkdir(parents=True, exist_ok=True)
        (self.config_dir / "skills").mkdir(parents=True, exist_ok=True)
        # Memory directories
        self.memory_dir.mkdir(parents=True, exist_ok=True)
        (self.memory_dir / "daily").mkdir(exist_ok=True)
        (self.memory_dir / "snapshots").mkdir(exist_ok=True)
        (self.memory_dir / "transcripts").mkdir(exist_ok=True)
        (self.memory_dir / "voices").mkdir(exist_ok=True)
        # PIM directories
        self.contacts_dir.mkdir(parents=True, exist_ok=True)
        self.calendar_dir.mkdir(parents=True, exist_ok=True)
        self.todos_dir.mkdir(parents=True, exist_ok=True)
        # One-time migration: memory/contacts/ → contacts/
        self._migrate_contacts()

    def _migrate_contacts(self) -> None:
        """Move .vcf files from legacy memory/contacts/ to top-level contacts/.

        Non-destructive: only moves if destination doesn't already exist.
        Cleans up the old directory if it's empty after migration.
        """
        old_dir = self.memory_dir / "contacts"
        new_dir = self.contacts_dir
        if not old_dir.exists():
            return
        for vcf in old_dir.glob("*.vcf"):
            dest = new_dir / vcf.name
            if not dest.exists():
                vcf.rename(dest)
        # Clean up the old directory if empty (no remaining files of any kind)
        if not any(old_dir.iterdir()):
            old_dir.rmdir()
