from __future__ import annotations

import json
import subprocess
from dataclasses import dataclass
from pathlib import Path
from typing import Optional


@dataclass
class ProjectInfo:
    name: str
    path: str
    project_type: str
    status: str
    startup: str


class ProcessTracker:
    def __init__(self) -> None:
        self._processes: dict[str, subprocess.Popen] = {}

    def remember(self, path: str | Path, process: subprocess.Popen) -> None:
        self._processes[str(Path(path).resolve())] = process

    def get(self, path: str | Path) -> Optional[subprocess.Popen]:
        return self._processes.get(str(Path(path).resolve()))

    def status(self, path: str | Path) -> str:
        process = self.get(path)
        if process is None:
            return 'offline'
        return 'online' if process.poll() is None else 'offline'


def _safe_read_json(path: Path) -> dict:
    try:
        return json.loads(path.read_text(encoding='utf-8'))
    except Exception:
        return {}


def find_startup_target(path: str | Path) -> str:
    root = Path(path)
    preferred = ['startup.cmd', 'start.cmd', 'run.cmd', 'launch.cmd', 'startup.bat', 'start.bat', 'run.bat', 'main.py', 'bot.py', 'app.py', 'server.py', 'index.py', 'main.js', 'index.js', 'server.js', 'package.json']
    for name in preferred:
        target = root / name
        if target.exists():
            return name
    for ext in ('.cmd', '.bat', '.py', '.js'):
        match = next(iter(sorted(root.glob(f'*{ext}'))), None)
        if match:
            return match.name
    return '-'


def detect_project_type(path: str | Path) -> str:
    root = Path(path)
    package = root / 'package.json'
    if package.exists():
        payload = _safe_read_json(package)
        deps = {**payload.get('dependencies', {}), **payload.get('devDependencies', {})}
        if 'discord.js' in deps or 'eris' in deps:
            return 'discord bot'
        if any(key in deps for key in ('next', 'react', 'vite', 'nuxt', 'svelte', 'express', 'fastify', 'koa')):
            return 'web'
        return 'node app'
    py_req = root / 'requirements.txt'
    if py_req.exists():
        req = py_req.read_text(encoding='utf-8', errors='ignore').lower()
        if any(x in req for x in ('discord.py', 'py-cord', 'nextcord', 'disnake')):
            return 'discord bot'
        if any(x in req for x in ('flask', 'fastapi', 'django', 'quart', 'starlette')):
            return 'web'
    pyproject = root / 'pyproject.toml'
    if pyproject.exists():
        content = pyproject.read_text(encoding='utf-8', errors='ignore').lower()
        if any(x in content for x in ('discord.py', 'py-cord', 'nextcord', 'disnake')):
            return 'discord bot'
        if any(x in content for x in ('flask', 'fastapi', 'django', 'quart', 'starlette')):
            return 'web'
    names = {p.name.lower() for p in root.iterdir()} if root.exists() else set()
    if {'bot.py', 'cogs'} & names:
        return 'discord bot'
    if {'app.py', 'wsgi.py', 'asgi.py', 'templates', 'static'} & names:
        return 'web'
    if {'server.py', 'server.js', 'plugins', 'mods', 'world'} & names:
        return 'server'
    if any((root / name).exists() for name in ('Dockerfile', 'docker-compose.yml', 'docker-compose.yaml')):
        return 'service'
    if any((root / name).exists() for name in ('main.py', 'app.py', 'bot.py')):
        return 'python app'
    if any((root / name).exists() for name in ('main.js', 'index.js')):
        return 'node app'
    return 'unknown'


def detect_status(path: str | Path, tracker: ProcessTracker | None = None) -> str:
    root = Path(path).resolve()
    if tracker and tracker.status(root) == 'online':
        return 'online'
    try:
        import psutil  # type: ignore
        for process in psutil.process_iter(['cmdline']):
            cmdline = ' '.join(process.info.get('cmdline') or [])
            if cmdline and str(root) in cmdline and process.is_running():
                return 'online'
    except Exception:
        pass
    return 'offline'


def scan_projects(base_dir: str | Path, tracker: ProcessTracker | None = None, include_hidden: bool = False) -> list[ProjectInfo]:
    base = Path(base_dir)
    if not base.exists():
        return []
    projects: list[ProjectInfo] = []
    for item in sorted(base.iterdir(), key=lambda p: p.name.lower()):
        if not item.is_dir():
            continue
        if not include_hidden and item.name.startswith('.'):
            continue
        startup = find_startup_target(item)
        projects.append(ProjectInfo(item.name, str(item), detect_project_type(item), detect_status(item, tracker), startup))
    return projects


def project_rows(base_dir: str | Path, tracker: ProcessTracker | None = None, include_hidden: bool = False) -> list[list[str]]:
    return [[p.name, p.project_type, p.status, p.startup] for p in scan_projects(base_dir, tracker, include_hidden)]
