"""Nix configuration and flake models."""

from __future__ import annotations

from enum import Enum

from pydantic import BaseModel, Field


class NixChannel(str, Enum):
    """Common Nix channels."""

    UNSTABLE = "nixpkgs-unstable"
    STABLE_24_11 = "nixos-24.11"
    STABLE_24_05 = "nixos-24.05"
    STABLE_23_11 = "nixos-23.11"


class NixConfig(BaseModel):
    """Nix configuration for a container.

    Attributes:
        enabled: Whether Nix is enabled for this container
        channel: Nix channel to use (default: nixpkgs-unstable)
        packages: List of Nix package names to install
        flake_url: Optional flake reference for environment
        experimental_features: Extra experimental features to enable
        binary_caches: Additional substituters for faster downloads
        trusted_public_keys: Public keys for binary caches
        max_jobs: Maximum parallel build jobs
        cores: Number of cores per build job
    """

    enabled: bool = False
    channel: str = NixChannel.UNSTABLE.value
    packages: list[str] = Field(default_factory=list)
    flake_url: str | None = None
    experimental_features: list[str] = Field(
        default_factory=lambda: ["nix-command", "flakes"]
    )
    binary_caches: list[str] = Field(default_factory=list)
    trusted_public_keys: list[str] = Field(default_factory=list)
    max_jobs: int = Field(default=4, ge=1)
    cores: int = Field(default=2, ge=1)

    def get_nix_conf(self) -> str:
        """Generate nix.conf content.

        Returns:
            nix.conf file content
        """
        lines = [
            f"experimental-features = {' '.join(self.experimental_features)}",
            f"max-jobs = {self.max_jobs}",
            f"cores = {self.cores}",
        ]

        if self.binary_caches:
            caches = " ".join(self.binary_caches)
            lines.append(f"substituters = https://cache.nixos.org {caches}")

        if self.trusted_public_keys:
            keys = " ".join(self.trusted_public_keys)
            lines.append(
                f"trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= {keys}"
            )

        return "\n".join(lines) + "\n"


class NixFlake(BaseModel):
    """Nix flake definition.

    Attributes:
        description: Flake description
        nixpkgs_ref: Reference to nixpkgs input
        packages: List of packages to include in devShell
        shell_hook: Optional shell hook script
        env_vars: Environment variables to set
        inputs: Additional flake inputs
    """

    description: str = "Gaol development environment"
    nixpkgs_ref: str = "github:NixOS/nixpkgs/nixos-24.11"
    packages: list[str] = Field(default_factory=list)
    shell_hook: str | None = None
    env_vars: dict[str, str] = Field(default_factory=dict)
    inputs: dict[str, str] = Field(default_factory=dict)

    def generate(self) -> str:
        """Generate flake.nix content.

        Returns:
            Complete flake.nix file content
        """
        # Build inputs section
        inputs_lines = [f'    nixpkgs.url = "{self.nixpkgs_ref}";']
        inputs_lines.append('    flake-utils.url = "github:numtide/flake-utils";')

        for name, url in self.inputs.items():
            inputs_lines.append(f'    {name}.url = "{url}";')

        inputs_section = "\n".join(inputs_lines)

        # Build packages list
        if self.packages:
            packages_lines = "\n".join(f"            {pkg}" for pkg in self.packages)
            packages_section = f"""packages = with pkgs; [
{packages_lines}
          ];"""
        else:
            packages_section = "packages = [ ];"

        # Build shell hook
        if self.shell_hook:
            # Escape the shell hook for Nix
            escaped_hook = self.shell_hook.replace("\\", "\\\\").replace('"', '\\"')
            shell_hook_section = f'''
          shellHook = ''
            {escaped_hook}
          '';'''
        else:
            shell_hook_section = ""

        # Build environment variables
        if self.env_vars:
            env_lines = "\n".join(
                f'          {key} = "{value}";' for key, value in self.env_vars.items()
            )
            env_section = f"\n{env_lines}"
        else:
            env_section = ""

        return f'''{{
  description = "{self.description}";

  inputs = {{
{inputs_section}
  }};

  outputs = {{ self, nixpkgs, flake-utils, ... }}:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${{system}};
      in
      {{
        devShells.default = pkgs.mkShell {{
          {packages_section}{shell_hook_section}{env_section}
        }};
      }}
    );
}}
'''


class NixEnvironment(BaseModel):
    """Represents a Nix shell environment.

    Attributes:
        name: Environment name
        packages: Packages to include
        pure: Whether to use pure mode (no system packages)
        command: Optional command to run
    """

    name: str
    packages: list[str] = Field(default_factory=list)
    pure: bool = False
    command: str | None = None

    def get_shell_command(self) -> str:
        """Generate the nix-shell command.

        Returns:
            Shell command string
        """
        parts = ["nix-shell"]

        if self.pure:
            parts.append("--pure")

        for pkg in self.packages:
            parts.extend(["-p", pkg])

        if self.command:
            parts.extend(["--run", f"'{self.command}'"])

        return " ".join(parts)

    def get_develop_command(self, flake_path: str = ".") -> str:
        """Generate the nix develop command.

        Args:
            flake_path: Path to flake directory

        Returns:
            Shell command string
        """
        parts = ["nix", "develop", flake_path]

        if self.command:
            parts.extend(["--command", self.command])

        return " ".join(parts)


class NixStatus(BaseModel):
    """Nix installation status in a container.

    Attributes:
        installed: Whether Nix is installed
        version: Nix version if installed
        channel: Current channel
        daemon_mode: Whether using daemon mode
        store_size: Size of /nix/store in bytes
        num_packages: Number of installed packages
        flake_support: Whether flakes are enabled
    """

    installed: bool = False
    version: str | None = None
    channel: str | None = None
    daemon_mode: bool = False
    store_size: int = 0
    num_packages: int = 0
    flake_support: bool = False

    def to_dict(self) -> dict:
        """Convert to dictionary for display.

        Returns:
            Dictionary representation
        """
        return {
            "installed": self.installed,
            "version": self.version or "N/A",
            "channel": self.channel or "N/A",
            "daemon_mode": self.daemon_mode,
            "store_size_mb": round(self.store_size / (1024 * 1024), 2),
            "num_packages": self.num_packages,
            "flake_support": self.flake_support,
        }
