"""Project registry — manages project sources and metadata."""
from __future__ import annotations

import enum
import json
import time
import uuid
from pathlib import Path
from typing import Any

from pydantic import BaseModel, Field


class SourceType(str, enum.Enum):
    local = "local"       # ~/github/proxym
    git = "git"           # git@github.com:user/repo.git
    ssh = "ssh"           # user@host:/path/to/project
    ftp = "ftp"           # ftp://host/path
    url = "url"           # https://github.com/user/repo (metadata only)


class ProjectStatus(str, enum.Enum):
    active = "active"
    archived = "archived"
    error = "error"


class Project(BaseModel):
    """Jeden projekt w systemie proxym."""
    id: str = Field(default_factory=lambda: f"proj-{uuid.uuid4().hex[:8]}")
    name: str                                    # "proxym", "my-app"
    source_type: SourceType = SourceType.local
    source_path: str                             # ścieżka/URL źródła
    local_path: str | None = None                # gdzie zmontowany/skolonowany lokalnie
    status: ProjectStatus = ProjectStatus.active

    # Metadata (auto-detected)
    has_git: bool = False
    stack: list[str] = Field(default_factory=list)     # ["python", "fastapi"]
    frameworks: list[str] = Field(default_factory=list) # ["react", "tailwind"]
    description: str = ""                              # krótki opis (opcjonalny)

    # Powiązania
    default_tool: str | None = None    # domyślne narzędzie: "aider", "claude-code"
    account_id: str | None = None      # domyślne konto (dla kosztów)
    vm_id: str | None = None           # powiązany VM

    # Statystyki
    ticket_count: int = 0
    last_activity: float | None = None
    created_at: float = Field(default_factory=time.time)

    # SSH/FTP credentials (opcjonalne)
    ssh_user: str | None = None
    ssh_host: str | None = None
    ssh_key_path: str | None = None
    ftp_port: int = 22


class ProjectRegistry:
    """Rejestr projektów z persystencją JSON."""

    def __init__(self, persist_path: Path | None = None) -> None:
        self._projects: dict[str, Project] = {}
        self._persist_path = persist_path
        if persist_path and persist_path.exists():
            self._load()

    # --- CRUD ---

    def add(self, project: Project) -> Project:
        """Dodaj projekt. Auto-detect metadata jeśli local."""
        if project.source_type == SourceType.local:
            project = self._enrich_local(project)
        self._projects[project.id] = project
        self._save()
        return project

    def get(self, project_id: str) -> Project | None:
        return self._projects.get(project_id)

    def find_by_name(self, name: str) -> Project | None:
        for p in self._projects.values():
            if p.name == name:
                return p
        return None

    def find_by_path(self, path: str) -> Project | None:
        for p in self._projects.values():
            if p.source_path == path or p.local_path == path:
                return p
        return None

    def list_projects(
        self,
        status: ProjectStatus | None = None,
        source_type: SourceType | None = None,
    ) -> list[Project]:
        projects = list(self._projects.values())
        if status:
            projects = [p for p in projects if p.status == status]
        if source_type:
            projects = [p for p in projects if p.source_type == source_type]
        return sorted(projects, key=lambda p: p.name)

    def update(self, project_id: str, updates: dict[str, Any]) -> Project | None:
        project = self._projects.get(project_id)
        if not project:
            return None
        for k, v in updates.items():
            if hasattr(project, k) and v is not None:
                setattr(project, k, v)
        project.last_activity = time.time()
        self._save()
        return project

    def remove(self, project_id: str) -> bool:
        if project_id in self._projects:
            del self._projects[project_id]
            self._save()
            return True
        return False

    # --- Scanning ---

    def scan_directory(self, root: str) -> list[Project]:
        """Skanuj folder i dodaj znalezione projekty (pomijaj istniejące)."""
        root_path = Path(root).expanduser().resolve()
        if not root_path.exists():
            return []
        added = []
        for child in sorted(root_path.iterdir()):
            if not child.is_dir() or child.name.startswith("."):
                continue
            # Pomiń jeśli już zarejestrowany
            if self.find_by_path(str(child)):
                continue
            project = Project(
                name=child.name,
                source_type=SourceType.local,
                source_path=str(child),
                local_path=str(child),
            )
            project = self._enrich_local(project)
            self._projects[project.id] = project
            added.append(project)
        if added:
            self._save()
        return added

    def add_git_repo(self, url: str, clone_to: str | None = None) -> Project:
        """Dodaj projekt z git repo. Klonuj jeśli clone_to podane."""
        import re
        # Wyciągnij nazwę z URL
        name = re.sub(r"\.git$", "", url.rsplit("/", 1)[-1])
        name = re.sub(r"[^a-zA-Z0-9_-]", "", name)

        project = Project(
            name=name,
            source_type=SourceType.git,
            source_path=url,
        )

        if clone_to:
            import subprocess
            target = Path(clone_to).expanduser() / name
            if not target.exists():
                subprocess.run(
                    ["git", "clone", url, str(target)],
                    check=True, capture_output=True, timeout=120,
                )
            project.local_path = str(target)
            project = self._enrich_local(project)

        self._projects[project.id] = project
        self._save()
        return project

    def add_ssh_project(
        self, host: str, path: str, user: str = "root",
        ssh_key: str | None = None, name: str | None = None,
    ) -> Project:
        """Dodaj zdalny projekt po SSH."""
        if not name:
            name = Path(path).name
        project = Project(
            name=name,
            source_type=SourceType.ssh,
            source_path=f"{user}@{host}:{path}",
            ssh_user=user,
            ssh_host=host,
            ssh_key_path=ssh_key,
        )
        self._projects[project.id] = project
        self._save()
        return project

    # --- Enrichment ---

    def _enrich_local(self, project: Project) -> Project:
        """Wzbogać metadata lokalnego projektu."""
        path = Path(project.local_path or project.source_path)
        if not path.exists():
            return project

        project.has_git = (path / ".git").exists()

        stack = []
        frameworks = []
        markers = {
            "pyproject.toml": ("python", None),
            "setup.py": ("python", None),
            "requirements.txt": ("python", None),
            "package.json": ("javascript", None),
            "tsconfig.json": ("typescript", None),
            "Cargo.toml": ("rust", None),
            "go.mod": ("go", None),
            "pom.xml": ("java", "maven"),
            "build.gradle": ("java", "gradle"),
            "Gemfile": ("ruby", "rails"),
            "mix.exs": ("elixir", "phoenix"),
            "docker-compose.yml": (None, "docker"),
            "Dockerfile": (None, "docker"),
        }
        for filename, (lang, fw) in markers.items():
            if (path / filename).exists():
                if lang and lang not in stack:
                    stack.append(lang)
                if fw and fw not in frameworks:
                    frameworks.append(fw)

        # Framework detection from content
        if (path / "package.json").exists():
            try:
                pkg = json.loads((path / "package.json").read_text())
                deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
                if "react" in deps: frameworks.append("react")
                if "vue" in deps: frameworks.append("vue")
                if "next" in deps: frameworks.append("nextjs")
                if "fastify" in deps: frameworks.append("fastify")
                if "express" in deps: frameworks.append("express")
            except Exception:
                pass

        if (path / "pyproject.toml").exists():
            try:
                text = (path / "pyproject.toml").read_text()
                if "fastapi" in text.lower(): frameworks.append("fastapi")
                if "django" in text.lower(): frameworks.append("django")
                if "flask" in text.lower(): frameworks.append("flask")
            except Exception:
                pass

        project.stack = stack
        project.frameworks = frameworks
        return project

    # --- Persistence ---

    def _save(self) -> None:
        if not self._persist_path:
            return
        self._persist_path.parent.mkdir(parents=True, exist_ok=True)
        data = {pid: p.model_dump() for pid, p in self._projects.items()}
        self._persist_path.write_text(json.dumps(data, indent=2, default=str))

    def _load(self) -> None:
        if not self._persist_path or not self._persist_path.exists():
            return
        try:
            data = json.loads(self._persist_path.read_text())
            for pid, d in data.items():
                self._projects[pid] = Project(**d)
        except Exception:
            pass
