"""AKS Command Executor - Executes kubectl and Azure CLI commands."""

import subprocess
import os
import json
from typing import Dict, Any, Tuple, Optional
from pathlib import Path


class AKSCommandExecutor:
    """Executes AKS deployment commands."""
    
    def __init__(self, default_namespace: str = "default", default_registry: str = None, llm_client: Optional[Any] = None, model_name: str = "gpt-4"):
        self.default_namespace = default_namespace
        self.default_registry = default_registry
        self.llm = llm_client
        self.model_name = model_name
    
    def execute_action(self, action_id: str, params: Dict[str, Any]) -> Tuple[bool, str, str]:
        """Execute an action with given parameters.
        
        Returns:
            Tuple of (success, stdout, stderr)
        """
        executor_map = {
            "deploy_application": self._deploy_application,
            "build_docker_image": self._build_docker_image,
            "push_to_acr": self._push_to_acr,
            "create_deployment": self._create_deployment,
            "create_service": self._create_service,
            "scale_deployment": self._scale_deployment,
            "rollback_deployment": self._rollback_deployment,
            "get_deployment_status": self._get_deployment_status,
        }
        
        executor = executor_map.get(action_id)
        if not executor:
            return False, "", f"Unknown action: {action_id}"
        
        return executor(params)
    
    def _run_command(self, command: str, cwd: str = None) -> Tuple[bool, str, str]:
        """Run shell command and return result."""
        # Check if Docker is running, start if not
        if "docker" in command.lower():
            self._ensure_docker_running()
        
        try:
            result = subprocess.run(
                command,
                shell=True,
                cwd=cwd,
                capture_output=True,
                text=True,
                encoding='utf-8',
                errors='ignore',
                timeout=600
            )
            return result.returncode == 0, result.stdout, result.stderr
        except subprocess.TimeoutExpired:
            return False, "", "Command timed out"
        except Exception as e:
            return False, "", str(e)
    
    def _ensure_docker_running(self):
        """Ensure Docker Desktop is running."""
        try:
            # Check if Docker is running
            result = subprocess.run(
                "docker info",
                shell=True,
                capture_output=True,
                timeout=5
            )
            if result.returncode == 0:
                return  # Docker is running
        except:
            pass
        
        # Start Docker Desktop
        print("Starting Docker Desktop...")
        try:
            subprocess.Popen(
                ["C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe"],
                shell=True
            )
            # Wait for Docker to start
            import time
            for i in range(30):
                time.sleep(2)
                try:
                    result = subprocess.run(
                        "docker info",
                        shell=True,
                        capture_output=True,
                        timeout=5
                    )
                    if result.returncode == 0:
                        print("Docker Desktop started successfully")
                        return
                except:
                    pass
            print("Warning: Docker may not be fully started yet")
        except Exception as e:
            print(f"Could not start Docker Desktop: {e}")
    
    def _deploy_application(self, params: Dict[str, Any]) -> Tuple[bool, str, str]:
        """Full deployment pipeline: build, push, deploy."""
        app_path = params['app_path']
        namespace = params.get('namespace', self.default_namespace)
        replicas = params.get('replicas', 1)
        tag = params.get('image_tag', 'latest')
        
        app_name = Path(app_path).name
        image_name = f"{app_name}:{tag}"
        
        outputs = []
        
        # Check kubectl connection (commented out - allows build/push without AKS connection)
        # Uncomment to enforce kubectl connection before deployment:
        success, stdout, stderr = self._run_command("kubectl cluster-info")
        if not success:
            return False, "", "Kubernetes cluster not connected. Run: az aks get-credentials --resource-group <rg> --name <cluster>"
        
        # Build
        print(f"\n[1/3] Building Docker image: {image_name}")
        success, stdout, stderr = self._build_docker_image({'app_path': app_path, 'image_name': app_name, 'tag': tag})
        outputs.append(f"=== BUILD ===\n{stdout}")
        if not success:
            return False, "\n\n".join(outputs), f"Build failed: {stderr}"
        print("✓ Build completed")
        
        # Push
        if self.default_registry:
            print(f"\n[2/3] Pushing to ACR: {self.default_registry}")
            full_image = f"{self.default_registry}/{image_name}"
            success, stdout, stderr = self._push_to_acr({'image_name': image_name, 'registry_name': self.default_registry, 'tag': tag})
            outputs.append(f"=== PUSH ===\n{stdout}")
            if not success:
                return False, "\n\n".join(outputs), f"Push failed: {stderr}"
            print("✓ Push completed")
        else:
            full_image = image_name
            print("\n[2/3] Skipping ACR push (no registry configured)")
        
        # Deploy
        print(f"\n[3/3] Deploying to AKS...")
        success, stdout, stderr = self._create_deployment({
            'app_name': app_name,
            'image': full_image,
            'namespace': namespace,
            'replicas': replicas
        })
        outputs.append(f"=== DEPLOY ===\n{stdout}")
        
        if not success:
            # Build/push succeeded but deploy failed
            outputs.append(f"\n⚠ Image built successfully but deployment failed (kubectl not connected)")
            outputs.append(f"\nTo deploy manually:\n  kubectl create deployment {app_name} --image={full_image} --replicas={replicas}")
            return False, "\n\n".join(outputs), stderr
        
        print("✓ Deployment completed")
        
        # Create service
        print(f"\n[4/4] Creating service...")
        svc_success, svc_stdout, _ = self._create_service({'app_name': app_name, 'namespace': namespace})
        outputs.append(f"=== SERVICE ===\n{svc_stdout}")
        if svc_success:
            print("✓ Service created")
        
        return success, "\n\n".join(outputs), stderr
    
    def _build_docker_image(self, params: Dict[str, Any]) -> Tuple[bool, str, str]:
        """Build Docker image."""
        app_path = params['app_path']
        image_name = params.get('image_name', Path(app_path).name)
        tag = params.get('tag', 'latest')
        
        # Check if Dockerfile exists, create if not
        dockerfile_path = Path(app_path) / "Dockerfile"
        if not dockerfile_path.exists():
            print(f"Scanning codebase in {app_path}...")
            dockerfile_content = self._generate_dockerfile(app_path)
            print(f"Creating Dockerfile...")
            dockerfile_path.write_text(dockerfile_content)
        
        command = f"docker build -t {image_name}:{tag} {app_path}"
        return self._run_command(command)
    
    def _generate_dockerfile(self, app_path: str) -> str:
        """Generate Dockerfile by analyzing codebase with LLM."""
        if self.llm:
            return self._generate_dockerfile_with_llm(app_path)
        return self._generate_dockerfile_fallback(app_path)
    
    def _scan_codebase(self, app_path: str) -> Dict[str, Any]:
        """Scan codebase and collect relevant information."""
        path = Path(app_path)
        info = {
            "files": [],
            "dependencies": {},
            "structure": []
        }
        
        # Scan files (limit to 50 files)
        for i, file in enumerate(path.rglob("*")):
            if i >= 50:
                break
            if file.is_file() and file.suffix in [".py", ".js", ".ts", ".go", ".java", ".json", ".txt", ".yml", ".yaml", ".xml"]:
                rel_path = file.relative_to(path)
                info["files"].append(str(rel_path))
                
                # Read dependency files
                if file.name in ["requirements.txt", "package.json", "go.mod", "pom.xml", "Pipfile", "pyproject.toml"]:
                    try:
                        content = file.read_text(encoding='utf-8', errors='ignore')[:2000]
                        info["dependencies"][file.name] = content
                    except:
                        pass
        
        # Get directory structure
        for item in path.iterdir():
            if item.is_dir() and item.name not in ["node_modules", "venv", ".git", "__pycache__"]:
                info["structure"].append(f"dir: {item.name}")
            elif item.is_file():
                info["structure"].append(f"file: {item.name}")
        
        return info
    
    def _generate_dockerfile_with_llm(self, app_path: str) -> str:
        """Use LLM to generate Dockerfile based on codebase analysis."""
        print("Analyzing codebase with LLM...")
        codebase_info = self._scan_codebase(app_path)
        
        prompt = f"""Analyze this codebase and generate an optimal Dockerfile.

Codebase structure:
{json.dumps(codebase_info, indent=2)}

Generate a production-ready Dockerfile. Return ONLY the Dockerfile content, no explanations.

Requirements:
- Use appropriate base image
- Install dependencies correctly
- Set proper WORKDIR
- Expose correct port
- Use optimal CMD/ENTRYPOINT
- Follow Docker best practices"""
        
        try:
            response = self.llm.chat.completions.create(
                model=self.model_name,
                messages=[
                    {"role": "system", "content": "You are a Docker expert. Generate production-ready Dockerfiles."},
                    {"role": "user", "content": prompt}
                ],
                temperature=0.1
            )
            
            if not response.choices or len(response.choices) == 0:
                print("LLM returned no response, using fallback")
                return self._generate_dockerfile_fallback(app_path)
            
            dockerfile = response.choices[0].message.content.strip()
            
            # Extract Dockerfile from markdown if present
            if "```dockerfile" in dockerfile.lower():
                dockerfile = dockerfile.split("```dockerfile")[1].split("```")[0].strip()
            elif "```" in dockerfile:
                dockerfile = dockerfile.split("```")[1].split("```")[0].strip()
            
            # Remove "Dockerfile" prefix if present
            if dockerfile.startswith("Dockerfile"):
                dockerfile = "\n".join(dockerfile.split("\n")[1:])
            
            return dockerfile
            
        except Exception as e:
            print(f"LLM generation failed: {e}, using fallback")
            return self._generate_dockerfile_fallback(app_path)
    
    def _generate_dockerfile_fallback(self, app_path: str) -> str:
        """Fallback Dockerfile generation using pattern matching."""
        path = Path(app_path)
        
        # Scan for files
        has_requirements = (path / "requirements.txt").exists()
        has_package_json = (path / "package.json").exists()
        has_go_mod = (path / "go.mod").exists()
        has_pom_xml = (path / "pom.xml").exists()
        
        # Find main file
        main_file = None
        for f in ["app.py", "main.py", "server.py", "index.js", "main.go", "Main.java"]:
            if (path / f).exists():
                main_file = f
                break
        
        # Generate based on detected language
        if has_requirements or main_file and main_file.endswith('.py'):
            return f"""FROM python:3.9
WORKDIR /app
COPY . /app
RUN pip install -r requirements.txt 2>/dev/null || echo "No requirements"
EXPOSE 80
CMD ["python", "{main_file or 'app.py'}"]
"""
        elif has_package_json:
            return """FROM node:16
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
"""
        elif has_go_mod:
            return """FROM golang:1.19
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
RUN go build -o main .
EXPOSE 8080
CMD ["./main"]
"""
        elif has_pom_xml:
            return """FROM maven:3.8-openjdk-11
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package
EXPOSE 8080
CMD ["java", "-jar", "target/*.jar"]
"""
        else:
            return """FROM python:3.9
WORKDIR /app
COPY . /app
EXPOSE 80
CMD ["python", "-m", "http.server", "80"]
"""
    
    def _push_to_acr(self, params: Dict[str, Any]) -> Tuple[bool, str, str]:
        """Push image to Azure Container Registry."""
        image_name = params['image_name']
        registry = params.get('registry_name', self.default_registry)
        tag = params.get('tag', 'latest')
        
        if not registry:
            return False, "", "Registry name not provided"
        
        # Tag image
        full_image = f"{registry}/{image_name}:{tag}"
        tag_cmd = f"docker tag {image_name}:{tag} {full_image}"
        success, stdout, stderr = self._run_command(tag_cmd)
        
        if not success:
            return False, stdout, stderr
        
        # Push image
        push_cmd = f"docker push {full_image}"
        return self._run_command(push_cmd)
    
    def _create_deployment(self, params: Dict[str, Any]) -> Tuple[bool, str, str]:
        """Create Kubernetes deployment."""
        app_name = params['app_name']
        image = params['image']
        namespace = params.get('namespace', self.default_namespace)
        replicas = params.get('replicas', 1)
        port = params.get('port', 80)
        
        command = f"""kubectl create deployment {app_name} \
--image={image} \
--replicas={replicas} \
--namespace={namespace} \
--port={port}"""
        
        return self._run_command(command)
    
    def _create_service(self, params: Dict[str, Any]) -> Tuple[bool, str, str]:
        """Create Kubernetes service."""
        app_name = params['app_name']
        namespace = params.get('namespace', self.default_namespace)
        port = params.get('port', 80)
        service_type = params.get('type', 'LoadBalancer')
        
        command = f"""kubectl expose deployment {app_name} \
--type={service_type} \
--port={port} \
--namespace={namespace}"""
        
        return self._run_command(command)
    
    def _scale_deployment(self, params: Dict[str, Any]) -> Tuple[bool, str, str]:
        """Scale Kubernetes deployment."""
        app_name = params['app_name']
        replicas = params['replicas']
        namespace = params.get('namespace', self.default_namespace)
        
        command = f"kubectl scale deployment {app_name} --replicas={replicas} --namespace={namespace}"
        return self._run_command(command)
    
    def _rollback_deployment(self, params: Dict[str, Any]) -> Tuple[bool, str, str]:
        """Rollback Kubernetes deployment."""
        app_name = params['app_name']
        namespace = params.get('namespace', self.default_namespace)
        revision = params.get('revision', '')
        
        command = f"kubectl rollout undo deployment/{app_name} --namespace={namespace}"
        if revision:
            command += f" --to-revision={revision}"
        
        return self._run_command(command)
    
    def _get_deployment_status(self, params: Dict[str, Any]) -> Tuple[bool, str, str]:
        """Get deployment status."""
        app_name = params['app_name']
        namespace = params.get('namespace', self.default_namespace)
        
        command = f"kubectl get deployment {app_name} --namespace={namespace} -o wide"
        return self._run_command(command)
