"""
Railpack blueprint - Railway's buildpack for universal project detection.

Downloads and runs the Railpack binary to analyze projects and generate
Dockerfile-based build plans.
"""

import json
import os
import platform
import subprocess
import sys
from pathlib import Path
from typing import Optional

from .base import Blueprint, BuildPlan


class RailpackBlueprint(Blueprint):
    """
    Railpack-based blueprint using Railway's buildpack.
    
    Railpack is a universal buildpack that can detect and build:
    - Python (FastAPI, Flask, Django, etc.)
    - Node.js (Next.js, Express, NestJS, etc.)
    - Go (Gin, Echo, etc.)
    - Rust (Actix, Axum, etc.)
    - Ruby, PHP, Java, and more
    
    Download URL: https://github.com/railwayapp/railpack/releases
    """
    
    name = "railpack"
    languages = ["python", "node", "go", "rust", "ruby", "php", "java", "dotnet"]
    
    # Version to download
    RAILPACK_VERSION = "0.0.70"
    
    def __init__(self):
        self._binary_path: Optional[Path] = None
        self._cache_dir = Path.home() / ".xenfra" / "cache" / "railpack"
    
    def _get_binary_name(self) -> str:
        """Get the appropriate binary name for the current platform."""
        system = platform.system().lower()
        machine = platform.machine().lower()
        
        # Map machine arch
        if machine in ("amd64", "x86_64"):
            arch = "amd64"
        elif machine in ("arm64", "aarch64"):
            arch = "arm64"
        else:
            arch = "amd64"  # fallback
        
        # Map system
        if system == "darwin":
            os_name = "darwin"
        elif system == "linux":
            os_name = "linux"
        elif system == "windows":
            os_name = "windows"
        else:
            os_name = "linux"  # fallback
        
        ext = ".exe" if os_name == "windows" else ""
        return f"railpack_{self.RAILPACK_VERSION}_{os_name}_{arch}{ext}"
    
    def _get_download_url(self) -> str:
        """Get the download URL for the current platform."""
        binary_name = self._get_binary_name()
        return (
            f"https://github.com/railwayapp/railpack/releases/download/"
            f"v{self.RAILPACK_VERSION}/{binary_name}"
        )
    
    def _ensure_binary(self) -> Path:
        """
        Ensure the Railpack binary is available, download if needed.
        
        Returns:
            Path to the railpack binary
        """
        if self._binary_path and self._binary_path.exists():
            return self._binary_path
        
        # Create cache directory
        self._cache_dir.mkdir(parents=True, exist_ok=True)
        
        binary_name = self._get_binary_name()
        binary_path = self._cache_dir / "railpack"
        
        # Check if already cached
        if binary_path.exists():
            self._binary_path = binary_path
            return binary_path
        
        # Download binary
        import urllib.request
        
        url = self._get_download_url()
        temp_path = self._cache_dir / binary_name
        
        print(f"Downloading Railpack v{self.RAILPACK_VERSION}...")
        print(f"  URL: {url}")
        
        try:
            urllib.request.urlretrieve(url, temp_path)
            
            # Make executable (not needed on Windows)
            if platform.system() != "Windows":
                os.chmod(temp_path, 0o755)
            
            # Rename to standard name
            temp_path.rename(binary_path)
            
            print(f"  Saved to: {binary_path}")
            
            self._binary_path = binary_path
            return binary_path
            
        except Exception as e:
            raise RuntimeError(f"Failed to download Railpack: {e}")
    
    def detect(self, project_path: Path) -> bool:
        """
        Railpack can handle any project - it's universal.
        
        Args:
            project_path: Path to the project directory
            
        Returns:
            Always True (fallback blueprint)
        """
        return True
    
    def generate_plan(self, project_path: Path) -> BuildPlan:
        """
        Generate a build plan using Railpack analysis.
        
        Args:
            project_path: Path to the project directory
            
        Returns:
            BuildPlan from Railpack analysis
            
        Raises:
            RuntimeError: If Railpack analysis fails
        """
        binary = self._ensure_binary()
        
        # Run railpack analyze
        try:
            result = subprocess.run(
                [str(binary), "analyze", str(project_path)],
                capture_output=True,
                text=True,
                timeout=60,
            )
            
            if result.returncode != 0:
                raise RuntimeError(f"Railpack analyze failed: {result.stderr}")
            
            # Parse the output as JSON
            analysis = json.loads(result.stdout)
            
            # Extract information from Railpack output
            metadata = analysis.get("metadata", {})
            
            # Detect framework from metadata
            framework = self._detect_framework(metadata, project_path)
            
            # Get package manager
            pkg_manager = metadata.get("package_manager", "unknown")
            
            # Get dependency file
            dep_file = self._get_dependency_file(pkg_manager, project_path)
            
            # Get port (common defaults)
            port = self._detect_port(metadata, framework)
            
            # Get start command from Railpack or infer
            start_cmd = self._get_start_command(analysis, framework, port)
            
            return BuildPlan(
                framework=framework,
                language=metadata.get("language", "unknown"),
                package_manager=pkg_manager,
                dependency_file=dep_file,
                build_command=None,  # Railpack handles this in the Dockerfile
                start_command=start_cmd,
                port=port,
                dockerfile=None,  # Generated by Railpack
                detected_from="railpack",
            )
            
        except json.JSONDecodeError as e:
            raise RuntimeError(f"Failed to parse Railpack output: {e}")
        except subprocess.TimeoutExpired:
            raise RuntimeError("Railpack analysis timed out")
        except Exception as e:
            raise RuntimeError(f"Railpack analysis failed: {e}")
    
    def generate_dockerfile(self, project_path: Path) -> str:
        """
        Generate a Dockerfile using Railpack.
        
        Args:
            project_path: Path to the project directory
            
        Returns:
            Generated Dockerfile content
        """
        binary = self._ensure_binary()
        
        try:
            result = subprocess.run(
                [str(binary), "build", str(project_path)],
                capture_output=True,
                text=True,
                timeout=120,
            )
            
            if result.returncode != 0:
                raise RuntimeError(f"Railpack build failed: {result.stderr}")
            
            return result.stdout
            
        except subprocess.TimeoutExpired:
            raise RuntimeError("Railpack build timed out")
        except Exception as e:
            raise RuntimeError(f"Railpack build failed: {e}")
    
    def _detect_framework(self, metadata: dict, project_path: Path) -> str:
        """Detect framework from Railpack metadata and project files."""
        # Check metadata hints
        hints = metadata.get("hints", [])
        
        for hint in hints:
            hint_lower = hint.lower()
            if "fastapi" in hint_lower:
                return "fastapi"
            elif "flask" in hint_lower:
                return "flask"
            elif "django" in hint_lower:
                return "django"
            elif "next" in hint_lower:
                return "nextjs"
            elif "express" in hint_lower:
                return "express"
            elif "nest" in hint_lower:
                return "nestjs"
        
        # Check for specific files
        if (project_path / "next.config.js").exists() or (project_path / "next.config.ts").exists():
            return "nextjs"
        
        # Default to language
        lang = metadata.get("language", "unknown")
        return lang if lang != "unknown" else "generic"
    
    def _get_dependency_file(self, package_manager: str, project_path: Path) -> str:
        """Get the dependency file name based on package manager."""
        mapping = {
            "pip": "requirements.txt",
            "poetry": "pyproject.toml",
            "pipenv": "Pipfile",
            "uv": "pyproject.toml",
            "npm": "package.json",
            "yarn": "package.json",
            "pnpm": "package.json",
            "bun": "package.json",
            "go": "go.mod",
            "cargo": "Cargo.toml",
            "bundler": "Gemfile",
            "composer": "composer.json",
        }
        return mapping.get(package_manager, "requirements.txt")
    
    def _detect_port(self, metadata: dict, framework: str) -> int:
        """Detect the port based on framework and metadata."""
        # Check if port is specified in environment
        for env in metadata.get("env", []):
            if "PORT" in env:
                try:
                    return int(env.split("=")[1])
                except (IndexError, ValueError):
                    pass
        
        # Framework defaults
        framework_ports = {
            "fastapi": 8000,
            "flask": 5000,
            "django": 8000,
            "nextjs": 3000,
            "express": 3000,
            "nestjs": 3000,
        }
        
        return framework_ports.get(framework, 8000)
    
    def _get_start_command(self, analysis: dict, framework: str, port: int) -> Optional[str]:
        """Get the start command from Railpack analysis or infer it."""
        # Check if Railpack provided a start command
        commands = analysis.get("commands", {})
        if "start" in commands:
            return commands["start"]
        
        # Infer based on framework
        cmds = {
            "fastapi": f"uvicorn main:app --host 0.0.0.0 --port {port}",
            "flask": f"flask run --host=0.0.0.0 --port={port}",
            "django": f"python manage.py runserver 0.0.0.0:{port}",
            "nextjs": "npm start",
            "express": "node server.js",
            "nestjs": "npm run start:prod",
        }
        
        return cmds.get(framework)
