"""
koci Jenkins Exporter

Exports koci pipelines to Jenkinsfile format.
"""

import re
from typing import Optional

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


class JenkinsExporter(BaseExporter):
    """
    Exporter for Jenkins pipelines.

    Generates declarative Jenkinsfile syntax.
    """

    platform = "jenkins"
    extension = ""
    default_filename = "Jenkinsfile"

    def __init__(self, declarative: bool = True):
        """
        Initialize exporter.

        Args:
            declarative: Use declarative pipeline syntax (vs scripted)
        """
        super().__init__()
        self.declarative = declarative

    def export(self, pipeline: Pipeline) -> str:
        """Export pipeline to Jenkinsfile format."""
        # Apply defaults to pipeline
        pipeline = self._apply_defaults(pipeline)

        # Detect secrets in the pipeline first
        self._detect_secrets_in_pipeline(pipeline, set())

        if self.declarative:
            return self._export_declarative(pipeline)
        else:
            return self._export_scripted(pipeline)

    def _export_declarative(self, pipeline: Pipeline) -> str:
        """Generate declarative Jenkinsfile."""
        lines = [
            "// Generated by koci - Universal CI Definition Tool",
            "// https://github.com/kodo/koci",
            "//",
            "// 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 {",
            "    agent any",
            "",
        ])

        # Global environment - handle secrets with credentials()
        if pipeline.env:
            lines.append("    environment {")
            for key, value in pipeline.env.items():
                env_line = self._format_env_var(key, value)
                lines.append(f"        {env_line}")
            lines.append("    }")
            lines.append("")

        # Options
        lines.append("    options {")
        lines.append("        buildDiscarder(logRotator(numToKeepStr: '10'))")
        lines.append("        timestamps()")
        lines.append("        disableConcurrentBuilds()")
        lines.append("    }")
        lines.append("")

        # Triggers
        if pipeline.on:
            lines.extend(self._generate_triggers(pipeline.on))
            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("            cleanWs()")
        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-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.",
            "",
        ])

        return '\n'.join(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 = f"{job.name}"
        lines.append(f"{prefix}stage('{stage_name}') {{")

        # Agent with Docker
        lines.append(f"{prefix}    agent {{")
        lines.append(f"{prefix}        docker {{")
        lines.append(f"{prefix}            image '{job.image}'")
        # Handle working directory
        if job.working_directory and job.working_directory != '/workspace':
            workspace_args = f"'-v ${{WORKSPACE}}:{job.working_directory} -w {job.working_directory}'"
        else:
            workspace_args = "'-v ${WORKSPACE}:/workspace -w /workspace'"
        lines.append(f"{prefix}            args {workspace_args}")
        lines.append(f"{prefix}        }}")
        lines.append(f"{prefix}    }}")

        # 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
        if job.matrix:
            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}        }}")

            # Handle exclude combinations
            if job.matrix.exclude:
                lines.append(f"{prefix}        excludes {{")
                for exc in job.matrix.exclude:
                    lines.append(f"{prefix}            exclude {{")
                    for var, val in exc.items():
                        lines.append(f"{prefix}                axis {{")
                        lines.append(f"{prefix}                    name '{var.upper()}'")
                        lines.append(f"{prefix}                    values '{val}'")
                        lines.append(f"{prefix}                }}")
                    lines.append(f"{prefix}            }}")
                lines.append(f"{prefix}        }}")

            lines.append(f"{prefix}        stages {{")
            lines.append(f"{prefix}            stage('Matrix Build') {{")
            # Steps go here
            lines.extend(self._generate_steps(job, pipeline, indent + 16))
            lines.append(f"{prefix}            }}")
            lines.append(f"{prefix}        }}")
            lines.append(f"{prefix}    }}")
        else:
            # Environment - handle secrets properly
            job_env = {**pipeline.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
            lines.extend(self._generate_steps(job, pipeline, indent + 4))

        # Post - archive artifacts and publish reports
        has_artifacts = job.artifacts and job.artifacts.paths
        has_reports = job.reports and (job.reports.junit or job.reports.coverage)

        if has_artifacts or has_reports:
            lines.append(f"{prefix}    post {{")
            lines.append(f"{prefix}        always {{")

            # JUnit reports
            if job.reports and job.reports.junit:
                lines.append(f"{prefix}            junit testResults: '{job.reports.junit}', allowEmptyResults: true")

            # Coverage reports
            if job.reports and job.reports.coverage:
                cov = job.reports.coverage
                lines.append(f"{prefix}            publishCoverage adapters: [coberturaAdapter(path: '{cov.path}')]")
                if cov.threshold:
                    lines.append(f"{prefix}            // Coverage threshold: {cov.threshold}%")

            # Archive artifacts
            if has_artifacts:
                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_steps(self, job: Job, pipeline: Pipeline, indent: int) -> list[str]:
        """Generate steps block for a job."""
        prefix = ' ' * indent
        lines = []

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

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

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

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

        return lines

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

        # 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 = indent

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

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

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

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

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

        # Close wrappers in reverse order
        if has_workdir:
            current_indent -= 4
            prefix = ' ' * current_indent
            lines.append(f"{prefix}}}")
        if has_env:
            current_indent -= 4
            prefix = ' ' * current_indent
            lines.append(f"{prefix}}}")
        if has_timeout:
            current_indent -= 4
            prefix = ' ' * current_indent
            lines.append(f"{prefix}}}")
        if has_continue:
            current_indent -= 4
            prefix = ' ' * current_indent
            lines.append(f"{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 _generate_triggers(self, triggers: dict) -> list[str]:
        """Generate Jenkins triggers block from koci triggers."""
        lines = ["    triggers {"]

        # Handle cron/schedule triggers
        if 'schedule' in triggers:
            schedule = triggers['schedule']
            if isinstance(schedule, str):
                lines.append(f"        cron('{schedule}')")
            elif isinstance(schedule, list):
                for s in schedule:
                    if isinstance(s, dict) and 'cron' in s:
                        lines.append(f"        cron('{s['cron']}')")
                    elif isinstance(s, str):
                        lines.append(f"        cron('{s}')")

        # Handle push trigger as pollSCM
        if 'push' in triggers:
            push = triggers['push']
            if isinstance(push, dict):
                branches = push.get('branches', [])
                if branches:
                    # Note: Jenkins uses pollSCM for this
                    lines.append("        pollSCM('H/5 * * * *')  // Poll for changes")

        # Handle pull_request trigger
        if 'pull_request' in triggers:
            # Jenkins typically uses GitHub/GitLab plugins for PR triggers
            lines.append("        // Pull request triggers require GitHub/GitLab plugin configuration")

        # Handle webhook trigger
        if 'webhook' in triggers:
            lines.append("        // Webhook triggers require webhook configuration")

        lines.append("    }")
        return lines

    def _export_scripted(self, pipeline: Pipeline) -> str:
        """Generate scripted Jenkinsfile (alternative format)."""
        lines = [
            "// Generated by koci - Universal CI Definition Tool",
            "// Scripted Pipeline",
            "",
            "node {",
        ]

        # Environment
        if pipeline.env:
            lines.append("    withEnv([")
            env_items = [f"        '{k}={v}'" for k, v in pipeline.env.items()]
            lines.append(',\n'.join(env_items))
            lines.append("    ]) {")

        # Stages
        for stage in pipeline.stages:
            lines.append(f"    stage('{stage.name}') {{")
            for job in stage.jobs:
                lines.append(f"        docker.image('{job.image}').inside {{")
                for step in job.steps:
                    if '\n' in step.run:
                        lines.append("            sh '''")
                        for line in step.run.split('\n'):
                            lines.append(f"                {line}")
                        lines.append("            '''")
                    else:
                        lines.append(f"            sh '{step.run}'")
                lines.append("        }")
            lines.append("    }")

        if pipeline.env:
            lines.append("    }")

        lines.append("}")

        return '\n'.join(lines)
