"""
koci Jenkins Kubernetes Exporter

Exports koci pipelines to Jenkinsfile format using the Kubernetes plugin.
Runs jobs in Kubernetes pods instead of Docker containers.
"""

from typing import Optional
import re

from .base import BaseExporter
from ..core.models import Pipeline, Job, Step, Stage


class JenkinsK8sExporter(BaseExporter):
    """
    Exporter for Jenkins with Kubernetes plugin.

    Generates declarative Jenkinsfile syntax using kubernetes agent
    with podTemplate for container execution.
    """

    platform = "jenkins-k8s"
    extension = ""
    default_filename = "Jenkinsfile"

    def __init__(
        self,
        namespace: str = "jenkins",
        service_account: str = "jenkins",
        default_memory: str = "512Mi",
        default_cpu: str = "500m",
    ):
        """
        Initialize exporter.

        Args:
            namespace: Kubernetes namespace for pods
            service_account: Service account for pods
            default_memory: Default memory limit for containers
            default_cpu: Default CPU limit for containers
        """
        super().__init__()
        self.namespace = namespace
        self.service_account = service_account
        self.default_memory = default_memory
        self.default_cpu = default_cpu

    def export(self, pipeline: Pipeline) -> str:
        """Export pipeline to Jenkinsfile format with Kubernetes agent."""
        # Detect secrets in the pipeline first
        self._detect_secrets_in_pipeline(pipeline, set())

        # Collect all unique containers needed across the pipeline
        containers = self._collect_all_containers(pipeline)

        lines = [
            "// Generated by koci - Universal CI Definition Tool",
            "// https://github.com/kodo/koci",
            "//",
            "// Jenkins Pipeline with Kubernetes Plugin",
            "// This Jenkinsfile was converted from a koci pipeline.",
            "// Edit the original koci.yml and re-export to update.",
        ]

        # Add secrets setup instructions as comments if secrets detected
        if self.detected_secrets:
            lines.append("//")
            lines.append("// ⚠️  SECRETS REQUIRED - Configure these in Jenkins:")
            lines.append("//     Jenkins → Manage Jenkins → Manage Credentials")
            lines.append("//")
            for secret_name, secret_info in self.detected_secrets.items():
                lines.append(f"//     - {secret_name}: {secret_info.description}")
            lines.append("//")

        lines.extend([
            "",
            "pipeline {",
        ])

        # Define pod template at pipeline level with all containers
        lines.extend(self._generate_pipeline_agent(containers, pipeline))
        lines.append("")

        # Global environment - handle secrets with credentials()
        env_vars = {}
        secrets = {}
        for key, value in pipeline.env.items():
            if self._is_secret(key):
                secrets[key] = key  # Use key as credential ID
            else:
                env_vars[key] = value

        # Add detected secrets that aren't already in env
        for secret_name in self.detected_secrets.keys():
            if secret_name not in secrets and secret_name not in env_vars:
                secrets[secret_name] = secret_name

        if env_vars or secrets:
            lines.append("    environment {")
            # Regular env vars
            for key, value in env_vars.items():
                lines.append(f"        {key} = '{value}'")
            # Secrets using credentials()
            for key, cred_id in secrets.items():
                lines.append(f"        {key} = credentials('{cred_id}')")
            lines.append("    }")
            lines.append("")

        # Options
        lines.append("    options {")
        lines.append("        buildDiscarder(logRotator(numToKeepStr: '10'))")
        lines.append("        timestamps()")
        lines.append("        timeout(time: 1, unit: 'HOURS')")
        lines.append("    }")
        lines.append("")

        # Stages
        lines.append("    stages {")

        for stage in pipeline.stages:
            lines.extend(self._generate_stage(stage, pipeline))

        lines.append("    }")
        lines.append("")

        # Post actions
        lines.append("    post {")
        lines.append("        always {")
        lines.append("            echo 'Pipeline completed'")
        lines.append("        }")
        lines.append("        success {")
        lines.append("            echo 'Pipeline completed successfully!'")
        lines.append("        }")
        lines.append("        failure {")
        lines.append("            echo 'Pipeline failed!'")
        lines.append("        }")
        lines.append("    }")

        lines.append("}")

        return '\n'.join(lines)

    def get_secrets_setup_instructions(self) -> str:
        """Get Jenkins K8s-specific instructions for setting up secrets."""
        if not self.detected_secrets:
            return ""

        lines = [
            "",
            "╔══════════════════════════════════════════════════════════════════╗",
            "║  🔐 JENKINS SECRETS CONFIGURATION REQUIRED                       ║",
            "╚══════════════════════════════════════════════════════════════════╝",
            "",
            "The following secrets need to be configured in Jenkins:",
            "",
        ]

        for secret_name, secret_info in self.detected_secrets.items():
            lines.append(f"  • {secret_name}")
            lines.append(f"    Description: {secret_info.description}")
            lines.append(f"    Used by: {', '.join(secret_info.required_by)}")
            lines.append("")

        lines.extend([
            "Setup Instructions:",
            "  1. Go to: Jenkins → Manage Jenkins → Manage Credentials",
            "  2. Select the appropriate credentials scope (e.g., 'Global')",
            "  3. Click 'Add Credentials'",
            "  4. For each secret above:",
            "     - Kind: 'Secret text'",
            "     - Secret: <your secret value>",
            "     - ID: <secret name exactly as shown above>",
            "     - Description: <optional description>",
            "",
            "Note: The credential ID must match the secret name exactly.",
            "",
            "For Kubernetes secrets, you can also use:",
            "  - Kubernetes Secrets mounted as environment variables",
            "  - External secrets operators (Vault, AWS Secrets Manager, etc.)",
            "",
        ])

        return '\n'.join(lines)

    def _collect_all_containers(self, pipeline: Pipeline) -> dict:
        """Collect all unique containers needed across the pipeline."""
        containers = {}

        for stage in pipeline.stages:
            # Add service containers
            for service in stage.services:
                name = service.name or self._sanitize_container_name(service.image)
                if name not in containers:
                    containers[name] = {
                        'image': service.image,
                        'env': service.env,
                        'ports': service.ports,
                        'is_service': True,
                    }

            # Add job containers
            for job in stage.jobs:
                # Handle matrix expansion
                expanded_jobs = job.get_expanded_jobs()
                for exp_job in expanded_jobs:
                    container_name = self._sanitize_container_name(exp_job.image)
                    if container_name not in containers:
                        containers[container_name] = {
                            'image': exp_job.image,
                            'env': {},
                            'ports': [],
                            'is_service': False,
                        }

        return containers

    def _generate_pipeline_agent(self, containers: dict, pipeline: Pipeline) -> list[str]:
        """Generate the pipeline-level Kubernetes agent with all containers."""
        lines = []

        lines.append("    agent {")
        lines.append("        kubernetes {")
        lines.append("            yaml '''")
        lines.append("apiVersion: v1")
        lines.append("kind: Pod")
        lines.append("metadata:")
        lines.append("  labels:")
        lines.append("    jenkins: agent")
        lines.append(f"    pipeline: {self._sanitize_label(pipeline.name)}")
        lines.append("spec:")
        lines.append(f"  serviceAccountName: {self.service_account}")
        lines.append("  containers:")

        # Add JNLP container (Jenkins agent)
        lines.append("  - name: jnlp")
        lines.append("    image: jenkins/inbound-agent:latest")
        lines.append("    args: ['$(JENKINS_SECRET)', '$(JENKINS_NAME)']")

        # Add all job and service containers
        for name, config in containers.items():
            lines.append(f"  - name: {name}")
            lines.append(f"    image: {config['image']}")

            if not config['is_service']:
                # Job containers need to stay alive
                lines.append("    command:")
                lines.append("    - cat")
                lines.append("    tty: true")

            # Environment variables
            if config.get('env'):
                lines.append("    env:")
                for key, value in config['env'].items():
                    lines.append(f"    - name: {key}")
                    lines.append(f"      value: \"{value}\"")

            # Ports for services
            if config.get('ports'):
                lines.append("    ports:")
                for port_spec in config['ports']:
                    if ':' in str(port_spec):
                        _, container_port = str(port_spec).split(':')
                    else:
                        container_port = port_spec
                    lines.append(f"    - containerPort: {container_port}")

            # Resource limits
            lines.append("    resources:")
            lines.append("      limits:")
            lines.append(f"        memory: {self.default_memory}")
            lines.append(f"        cpu: {self.default_cpu}")
            lines.append("      requests:")
            lines.append("        memory: 256Mi")
            lines.append("        cpu: 100m")

        lines.append("'''")
        lines.append("        }")
        lines.append("    }")

        return lines

    def _generate_stage(self, stage: Stage, pipeline: Pipeline) -> list[str]:
        """Generate a Jenkins stage block."""
        lines = []

        # If stage has multiple independent jobs, use parallel
        independent_jobs = [
            j for j in stage.jobs
            if not j.needs or not any(n in [jj.name for jj in stage.jobs] for n in j.needs)
        ]

        if len(stage.jobs) > 1 and len(independent_jobs) > 1:
            # Parallel jobs
            lines.append(f"        stage('{stage.name}') {{")
            lines.append("            parallel {")

            for job in stage.jobs:
                lines.extend(self._generate_job_stage(job, pipeline, indent=16))

            lines.append("            }")
            lines.append("        }")
        else:
            # Sequential jobs (each as a stage)
            for job in stage.jobs:
                lines.extend(self._generate_job_stage(job, pipeline, indent=8))

        return lines

    def _generate_job_stage(
        self,
        job: Job,
        pipeline: Pipeline,
        indent: int = 8,
    ) -> list[str]:
        """Generate a stage for a single job."""
        prefix = ' ' * indent
        lines = []

        stage_name = job.name
        lines.append(f"{prefix}stage('{stage_name}') {{")

        # Condition
        condition = self._convert_condition_jenkins(job)
        if condition:
            lines.append(f"{prefix}    when {{")
            lines.append(f"{prefix}        {condition}")
            lines.append(f"{prefix}    }}")

        # Matrix builds
        if job.matrix:
            lines.extend(self._generate_matrix(job, pipeline, prefix + "    "))
        else:
            # Environment
            job_env = {**job.env}
            if job_env:
                lines.append(f"{prefix}    environment {{")
                for key, value in job_env.items():
                    lines.append(f"{prefix}        {key} = '{value}'")
                lines.append(f"{prefix}    }}")

            # Steps - runs in the appropriate container
            lines.extend(self._generate_steps(job, pipeline, prefix + "    "))

        # Post - archive artifacts
        if job.artifacts and job.artifacts.paths:
            lines.append(f"{prefix}    post {{")
            lines.append(f"{prefix}        success {{")
            for path in job.artifacts.paths:
                lines.append(
                    f"{prefix}            archiveArtifacts artifacts: '{path}', fingerprint: true"
                )
            lines.append(f"{prefix}        }}")
            lines.append(f"{prefix}    }}")

        lines.append(f"{prefix}}}")

        return lines

    def _generate_matrix(self, job: Job, pipeline: Pipeline, prefix: str) -> list[str]:
        """Generate matrix build configuration."""
        lines = []

        lines.append(f"{prefix}matrix {{")
        lines.append(f"{prefix}    axes {{")

        for var, values in job.matrix.variables.items():
            lines.append(f"{prefix}        axis {{")
            lines.append(f"{prefix}            name '{var.upper()}'")
            values_str = ', '.join(f"'{v}'" for v in values)
            lines.append(f"{prefix}            values {values_str}")
            lines.append(f"{prefix}        }}")

        lines.append(f"{prefix}    }}")

        # Exclude combinations if specified
        if job.matrix.exclude:
            lines.append(f"{prefix}    excludes {{")
            for exclude in job.matrix.exclude:
                lines.append(f"{prefix}        exclude {{")
                for key, value in exclude.items():
                    lines.append(f"{prefix}            axis {{")
                    lines.append(f"{prefix}                name '{key.upper()}'")
                    lines.append(f"{prefix}                values '{value}'")
                    lines.append(f"{prefix}            }}")
                lines.append(f"{prefix}        }}")
            lines.append(f"{prefix}    }}")

        lines.append(f"{prefix}    stages {{")
        lines.append(f"{prefix}        stage('Matrix Build') {{")
        lines.extend(self._generate_steps(job, pipeline, prefix + "            "))
        lines.append(f"{prefix}        }}")
        lines.append(f"{prefix}    }}")
        lines.append(f"{prefix}}}")

        return lines

    def _generate_steps(self, job: Job, pipeline: Pipeline, prefix: str) -> list[str]:
        """Generate steps block for a job."""
        lines = []

        lines.append(f"{prefix}steps {{")

        # Container block to ensure we run in the right container
        container_name = self._sanitize_container_name(job.image)
        lines.append(f"{prefix}    container('{container_name}') {{")

        # Copy artifacts if consuming
        if job.artifacts and job.artifacts.consume:
            lines.append(f"{prefix}        copyArtifacts(")
            lines.append(f"{prefix}            projectName: env.JOB_NAME,")
            lines.append(f"{prefix}            selector: specific(env.BUILD_NUMBER),")
            lines.append(f"{prefix}            filter: '**/*',")
            lines.append(f"{prefix}            target: '.',")
            lines.append(f"{prefix}            flatten: false")
            lines.append(f"{prefix}        )")

        # Execute steps
        for step in job.steps:
            step_lines = self._generate_step(step, prefix + "        ")
            lines.extend(step_lines)

        lines.append(f"{prefix}    }}")
        lines.append(f"{prefix}}}")

        return lines

    def _generate_step(self, step: Step, prefix: str) -> list[str]:
        """Generate a single step."""
        lines = []

        # Add comment with step name
        if step.name:
            lines.append(f"{prefix}// {step.name}")

        # Shell command
        if '\n' in step.run:
            # Multi-line script
            lines.append(f"{prefix}sh '''")
            for line in step.run.strip().split('\n'):
                lines.append(f"{prefix}    {line}")
            lines.append(f"{prefix}'''")
        else:
            # Single line
            escaped = step.run.replace("'", "\\'")
            lines.append(f"{prefix}sh '{escaped}'")

        return lines

    def _convert_condition_jenkins(self, job: Job) -> Optional[str]:
        """Convert job condition to Jenkins when syntax."""
        if not job.when and not job.condition:
            return None

        conditions = []

        if job.when:
            when = job.when.strip()

            # branch == 'main'
            if 'branch ==' in when:
                match = re.search(r"branch\s*==\s*['\"](.+?)['\"]", when)
                if match:
                    conditions.append(f"branch '{match.group(1)}'")

            # branch != 'main' -> not { branch 'main' }
            elif 'branch !=' in when:
                match = re.search(r"branch\s*!=\s*['\"](.+?)['\"]", when)
                if match:
                    conditions.append(f"not {{ branch '{match.group(1)}' }}")

            # tag =~ 'v*'
            elif 'tag =~' in when:
                match = re.search(r"tag\s*=~\s*['\"](.+?)['\"]", when)
                if match:
                    pattern = match.group(1)
                    conditions.append(f"tag pattern: '{pattern}', comparator: 'GLOB'")

            # always
            elif when.lower() == 'always':
                return None  # No condition needed

            # never
            elif when.lower() == 'never':
                return "expression { return false }"

        if not conditions:
            return None

        if len(conditions) == 1:
            return conditions[0]

        return "allOf { " + "; ".join(conditions) + " }"

    def _sanitize_container_name(self, image: str) -> str:
        """Sanitize Docker image name for use as Kubernetes container name."""
        # Extract just the image name without registry/tag
        name = image.split('/')[-1].split(':')[0]
        # Replace invalid characters
        name = re.sub(r'[^a-z0-9-]', '-', name.lower())
        # Ensure it starts with a letter
        if name[0].isdigit() or name[0] == '-':
            name = 'c-' + name
        return name[:63]  # Kubernetes name limit

    def _sanitize_label(self, name: str) -> str:
        """Sanitize a name for use as Kubernetes label value."""
        # Replace invalid characters
        name = re.sub(r'[^a-zA-Z0-9-_.]', '-', name)
        # Ensure it starts and ends with alphanumeric
        name = name.strip('-_.')
        if not name or not name[0].isalnum():
            name = 'j-' + name
        return name[:63]
