"""
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."""
        # Apply defaults to pipeline
        pipeline = self._apply_defaults(pipeline)

        # 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 stage-level 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,
                        'healthcheck': getattr(service, 'healthcheck', None),
                        'is_service': True,
                    }

            # Add job containers and job-level services
            for job in stage.jobs:
                # Add job-level service containers (sidecars)
                if job.services:
                    for service in job.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,
                                'healthcheck': getattr(service, 'healthcheck', None),
                                'is_service': True,
                            }

                # 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': [],
                            'healthcheck': None,
                            '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:")
                first_port = None
                for port_spec in config['ports']:
                    if ':' in str(port_spec):
                        _, container_port = str(port_spec).split(':')
                    else:
                        container_port = port_spec
                    if first_port is None:
                        first_port = container_port
                    lines.append(f"    - containerPort: {container_port}")

                # Add readiness probe for services (on first port)
                if config['is_service'] and first_port:
                    lines.append("    readinessProbe:")
                    lines.append("      tcpSocket:")
                    lines.append(f"        port: {first_port}")
                    lines.append("      initialDelaySeconds: 5")
                    lines.append("      periodSeconds: 5")

            # 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}') {{")

        # Options for timeout and retry
        if job.timeout_minutes != 60 or job.retry > 0:
            lines.append(f"{prefix}    options {{")
            if job.timeout_minutes != 60:
                lines.append(f"{prefix}        timeout(time: {job.timeout_minutes}, unit: 'MINUTES')")
            if job.retry > 0:
                lines.append(f"{prefix}        retry({job.retry})")
            lines.append(f"{prefix}    }}")

        # 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 - handle secrets properly
            job_env = {**job.env}
            if job_env:
                lines.append(f"{prefix}    environment {{")
                for key, value in job_env.items():
                    env_line = self._format_env_var(key, value)
                    lines.append(f"{prefix}        {env_line}")
                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 = []
        base_indent = len(prefix)

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

        # Determine wrapping needed
        has_env = bool(step.env)
        has_workdir = step.working_directory and step.working_directory != '/workspace'
        has_timeout = step.timeout_minutes and step.timeout_minutes > 0
        has_continue = step.continue_on_error

        current_indent = base_indent

        # Wrap with catchError for continue_on_error
        if has_continue:
            current_prefix = ' ' * current_indent
            lines.append(f"{current_prefix}catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {{")
            current_indent += 4

        # Wrap with timeout if specified
        if has_timeout:
            current_prefix = ' ' * current_indent
            lines.append(f"{current_prefix}timeout(time: {step.timeout_minutes}, unit: 'MINUTES') {{")
            current_indent += 4

        # Wrap with withEnv for step-level env
        if has_env:
            current_prefix = ' ' * current_indent
            env_items = [f"'{k}={v}'" for k, v in step.env.items()]
            lines.append(f"{current_prefix}withEnv([{', '.join(env_items)}]) {{")
            current_indent += 4

        # Wrap with dir for working_directory
        if has_workdir:
            current_prefix = ' ' * current_indent
            lines.append(f"{current_prefix}dir('{step.working_directory}') {{")
            current_indent += 4

        current_prefix = ' ' * current_indent

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

        # Close wrappers in reverse order
        if has_workdir:
            current_indent -= 4
            current_prefix = ' ' * current_indent
            lines.append(f"{current_prefix}}}")
        if has_env:
            current_indent -= 4
            current_prefix = ' ' * current_indent
            lines.append(f"{current_prefix}}}")
        if has_timeout:
            current_indent -= 4
            current_prefix = ' ' * current_indent
            lines.append(f"{current_prefix}}}")
        if has_continue:
            current_indent -= 4
            current_prefix = ' ' * current_indent
            lines.append(f"{current_prefix}}}")

        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 _format_env_var(self, key: str, value: str) -> str:
        """
        Format an environment variable for Jenkins, handling secrets.

        Detects:
        - ${SECRET_NAME} references -> credentials('SECRET_NAME')
        - Secret-named keys with non-secret values -> credentials('key')
        - Regular values -> quoted strings
        """
        # Check if value is a variable reference like ${PYPI_TOKEN}
        ref_match = re.match(r'^\$\{([A-Z_][A-Z0-9_]*)\}$', str(value))
        if ref_match:
            ref_name = ref_match.group(1)
            # The referenced variable is the actual secret
            if self._is_secret(ref_name) or ref_name in self.detected_secrets:
                return f"{key} = credentials('{ref_name}')"

        # Check if the key itself looks like a secret
        if self._is_secret(key):
            # If value is a simple reference, use the referenced name as credential ID
            if ref_match:
                return f"{key} = credentials('{ref_match.group(1)}')"
            # Otherwise use the key itself
            return f"{key} = credentials('{key}')"

        # Regular environment variable
        return f"{key} = '{value}'"

    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]
