"""Port allocation and .env.paracord generation for Paracord projects."""

import socket
from dataclasses import dataclass
from pathlib import Path

from paracord.core.project_registry import list_projects


# Base ports for slot calculation
# Starting at +100 offset to avoid conflicts with standard ports
DJANGO_BASE_PORT = 8100
POSTGRES_BASE_PORT = 5532
REDIS_BASE_PORT = 6479

# Maximum number of slots to try
MAX_SLOTS = 100


@dataclass
class PortSet:
    """A set of ports for a project."""

    django_port: int
    postgres_port: int
    redis_port: int
    slot: int

    @classmethod
    def from_slot(cls, slot: int) -> "PortSet":
        """Create a PortSet from a slot number."""
        return cls(
            django_port=DJANGO_BASE_PORT + slot,
            postgres_port=POSTGRES_BASE_PORT + slot,
            redis_port=REDIS_BASE_PORT + slot,
            slot=slot,
        )


def is_port_available(port: int) -> bool:
    """Check if a port is available (not in use).

    Args:
        port: Port number to check

    Returns:
        True if port is available, False if in use
    """
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.settimeout(0.1)
            s.bind(("127.0.0.1", port))
            return True
    except OSError:
        return False


def get_used_port_slots() -> set[int]:
    """Get set of port slots already used by registered projects.

    Calculates slot from django_port (port - DJANGO_BASE_PORT).

    Returns:
        Set of used slot numbers
    """
    projects = list_projects()
    slots = set()

    for project_info in projects.values():
        django_port = project_info.get("port", 0)
        if django_port >= DJANGO_BASE_PORT:
            slot = django_port - DJANGO_BASE_PORT
            slots.add(slot)

    return slots


def allocate_ports(skip_availability_check: bool = False) -> PortSet:
    """Allocate the next available port set for a new project.

    Args:
        skip_availability_check: If True, only check registry, not actual port usage

    Returns:
        PortSet with allocated ports

    Raises:
        RuntimeError: If no available slots found
    """
    used_slots = get_used_port_slots()

    for slot in range(MAX_SLOTS):
        if slot in used_slots:
            continue

        port_set = PortSet.from_slot(slot)

        # Check if ports are actually available
        if not skip_availability_check:
            ports_to_check = [
                port_set.django_port,
                port_set.postgres_port,
                port_set.redis_port,
            ]
            if not all(is_port_available(p) for p in ports_to_check):
                # Ports are in use by something else, skip this slot
                continue

        return port_set

    raise RuntimeError(
        f"No available port slots found (checked {MAX_SLOTS} slots). "
        "Consider removing unused projects with 'paracord console remove'."
    )


def write_env_file(project_path: Path, port_set: PortSet) -> Path:
    """Generate .env.paracord file with port configuration.

    Args:
        project_path: Path to the project directory
        port_set: PortSet with the allocated ports

    Returns:
        Path to the generated .env.paracord file
    """
    env_path = project_path / ".env.paracord"

    content = f"""\
# Managed by paracord - do not edit manually
# Run 'paracord sync' to regenerate from pyproject.toml

DJANGO_PORT={port_set.django_port}
POSTGRES_PORT={port_set.postgres_port}
REDIS_PORT={port_set.redis_port}
"""

    env_path.write_text(content)
    return env_path


def read_env_file(project_path: Path) -> PortSet | None:
    """Read port configuration from .env.paracord file.

    Args:
        project_path: Path to the project directory

    Returns:
        PortSet if file exists and is valid, None otherwise
    """
    env_path = project_path / ".env.paracord"

    if not env_path.exists():
        return None

    ports = {}
    for line in env_path.read_text().splitlines():
        line = line.strip()
        if not line or line.startswith("#"):
            continue
        if "=" in line:
            key, value = line.split("=", 1)
            key = key.strip()
            try:
                ports[key] = int(value.strip())
            except ValueError:
                continue

    django_port = ports.get("DJANGO_PORT")
    postgres_port = ports.get("POSTGRES_PORT")
    redis_port = ports.get("REDIS_PORT")

    if django_port is None:
        return None

    slot = django_port - DJANGO_BASE_PORT

    return PortSet(
        django_port=django_port,
        postgres_port=postgres_port or (POSTGRES_BASE_PORT + slot),
        redis_port=redis_port or (REDIS_BASE_PORT + slot),
        slot=slot,
    )
