"""
koci Concourse CI Exporter

Exports koci pipelines to Concourse CI format (pipeline.yml).
"""

import yaml
from typing import Any

from .base import BaseExporter
from ..core.models import Pipeline, Job, Service


class ConcourseExporter(BaseExporter):
    """
    Exporter for Concourse CI pipelines.

    Generates Concourse pipeline.yml format.
    """

    platform = "concourse"
    extension = ".yml"
    default_filename = "pipeline.yml"

    def __init__(self, git_resource: str = "source-code"):
        """
        Initialize exporter.

        Args:
            git_resource: Name for the git resource
        """
        super().__init__()
        self.git_resource = git_resource

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

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

        config: dict[str, Any] = {}

        # Resource types (for custom resources if needed)
        resource_types = self._generate_resource_types(pipeline)
        if resource_types:
            config['resource_types'] = resource_types

        # Resources (git, artifacts, etc.)
        config['resources'] = self._generate_resources(pipeline)

        # Jobs
        config['jobs'] = []
        for stage in pipeline.stages:
            for job in stage.jobs:
                job_config = self._convert_job(job, stage.services, pipeline)
                config['jobs'].append(job_config)

        # Groups (organize jobs by stage)
        groups = self._generate_groups(pipeline)
        if groups:
            config['groups'] = groups

        # Generate YAML
        return self._generate_yaml(config)

    def _generate_resource_types(self, pipeline: Pipeline) -> list[dict]:
        """Generate custom resource types if needed."""
        resource_types = []

        # Check if we need any custom resource types
        for stage in pipeline.stages:
            for job in stage.jobs:
                if job.reports and job.reports.junit:
                    # Add junit resource type for test reporting
                    resource_types.append({
                        'name': 'junit-report',
                        'type': 'docker-image',
                        'source': {
                            'repository': 'concourse/junit-resource',
                        }
                    })
                    break

        return resource_types

    def _generate_resources(self, pipeline: Pipeline) -> list[dict]:
        """Generate Concourse resources."""
        resources = []

        # Git source resource
        git_resource = {
            'name': self.git_resource,
            'type': 'git',
            'icon': 'github',
            'source': {
                'uri': '((git_url))',
                'branch': '((git_branch))',
            }
        }

        # Add trigger configuration if defined
        if pipeline.on:
            if 'push' in pipeline.on:
                git_resource['source']['branch'] = '((git_branch))'

        resources.append(git_resource)

        # Create artifact resources for jobs that produce artifacts
        artifact_jobs = set()
        for stage in pipeline.stages:
            for job in stage.jobs:
                if job.artifacts and job.artifacts.paths:
                    artifact_jobs.add(job.name)
                    # Create an artifact resource
                    resources.append({
                        'name': f'{self._sanitize_name(job.name)}-artifacts',
                        'type': 's3',
                        'icon': 'folder',
                        'source': {
                            'bucket': '((artifacts_bucket))',
                            'region_name': '((aws_region))',
                            'access_key_id': '((aws_access_key_id))',
                            'secret_access_key': '((aws_secret_access_key))',
                            'regexp': f'{self._sanitize_name(job.name)}/artifacts-(.*).tar.gz',
                        }
                    })

        return resources

    def _convert_job(self, job: Job, stage_services: list[Service], pipeline: Pipeline) -> dict:
        """Convert a koci job to Concourse job."""
        concourse_job: dict[str, Any] = {
            'name': self._sanitize_name(job.name),
        }

        # Serial execution
        concourse_job['serial'] = True

        # Plan (steps)
        plan: list[dict] = []

        # Get source code
        get_step: dict[str, Any] = {
            'get': self.git_resource,
        }

        # Add trigger if this is a first stage job
        if not job.needs:
            if pipeline.on:
                get_step['trigger'] = True

        plan.append(get_step)

        # Get artifacts from dependencies
        if job.artifacts and job.artifacts.consume:
            for consume_from in job.artifacts.consume:
                plan.append({
                    'get': f'{self._sanitize_name(consume_from)}-artifacts',
                    'passed': [self._sanitize_name(consume_from)],
                })

        # Dependencies (passed constraint)
        if job.needs:
            concourse_job['passed'] = [self._sanitize_name(n) for n in job.needs]

        # Main task
        task = self._create_task(job, stage_services, pipeline)
        plan.append(task)

        # Put artifacts if job produces them
        if job.artifacts and job.artifacts.paths:
            plan.append({
                'put': f'{self._sanitize_name(job.name)}-artifacts',
                'params': {
                    'file': f'{self.git_resource}/artifacts-*.tar.gz',
                }
            })

        concourse_job['plan'] = plan

        # On success/failure hooks
        if job.continue_on_error:
            concourse_job['on_failure'] = {
                'task': 'log-failure',
                'config': {
                    'platform': 'linux',
                    'image_resource': {
                        'type': 'docker-image',
                        'source': {'repository': 'alpine'},
                    },
                    'run': {
                        'path': 'sh',
                        'args': ['-c', 'echo "Job failed but continuing..."'],
                    }
                }
            }

        return concourse_job

    def _create_task(self, job: Job, stage_services: list[Service], pipeline: Pipeline) -> dict:
        """Create a Concourse task for the job."""
        task: dict[str, Any] = {
            'task': self._sanitize_name(job.name),
        }

        # Task config
        config: dict[str, Any] = {
            'platform': 'linux',
        }

        # Image resource
        config['image_resource'] = {
            'type': 'docker-image',
            'source': {
                'repository': job.image.split(':')[0],
                'tag': job.image.split(':')[1] if ':' in job.image else 'latest',
            }
        }

        # Inputs
        config['inputs'] = [
            {'name': self.git_resource},
        ]

        # Add artifact inputs if consuming
        if job.artifacts and job.artifacts.consume:
            for consume_from in job.artifacts.consume:
                config['inputs'].append({
                    'name': f'{self._sanitize_name(consume_from)}-artifacts',
                })

        # Outputs (for artifacts)
        if job.artifacts and job.artifacts.paths:
            config['outputs'] = [
                {'name': 'artifacts'},
            ]

        # Environment params
        env_vars = {**pipeline.env, **job.env}
        if env_vars:
            # Convert to Concourse params format (use (()) for secrets)
            params = {}
            for key, value in env_vars.items():
                if self._is_secret(key):
                    params[key] = f'(({key.lower()}))'
                else:
                    params[key] = value
            config['params'] = params

        # Run script
        script_lines = ['#!/bin/sh', 'set -ex', '']

        # Change to source directory
        script_lines.append(f'cd {self.git_resource}')
        script_lines.append('')

        # Add each step
        for step in job.steps:
            if step.name:
                script_lines.append(f'echo "=== {step.name} ==="')

            # Handle working directory
            if step.working_directory:
                script_lines.append(f'cd {step.working_directory}')

            # Handle step env
            if step.env:
                for key, value in step.env.items():
                    script_lines.append(f'export {key}="{value}"')

            # Add the command
            if '\n' in step.run:
                script_lines.extend(step.run.strip().split('\n'))
            else:
                script_lines.append(step.run)

            script_lines.append('')

        # Package artifacts if specified
        if job.artifacts and job.artifacts.paths:
            script_lines.append('# Package artifacts')
            script_lines.append('cd ..')
            paths_str = ' '.join(f'{self.git_resource}/{p}' for p in job.artifacts.paths)
            script_lines.append(f'tar -czf artifacts/artifacts-$BUILD_ID.tar.gz {paths_str}')

        config['run'] = {
            'path': 'sh',
            'args': ['-c', '\n'.join(script_lines)],
        }

        task['config'] = config

        # Timeout
        if job.timeout_minutes != 60:
            task['timeout'] = f'{job.timeout_minutes}m'

        # Retry
        if job.retry > 0:
            task['attempts'] = job.retry + 1

        return task

    def _generate_groups(self, pipeline: Pipeline) -> list[dict]:
        """Generate job groups by stage."""
        groups = []

        for stage in pipeline.stages:
            job_names = [self._sanitize_name(j.name) for j in stage.jobs]
            if job_names:
                groups.append({
                    'name': stage.name,
                    'jobs': job_names,
                })

        return groups

    def _generate_yaml(self, data: dict) -> str:
        """Generate nicely formatted YAML."""
        # Custom representer for multiline strings
        def str_representer(dumper, data):
            if '\n' in data:
                return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
            return dumper.represent_scalar('tag:yaml.org,2002:str', data)

        yaml.add_representer(str, str_representer)

        output = yaml.dump(
            data,
            default_flow_style=False,
            sort_keys=False,
            allow_unicode=True,
            width=120,
        )

        # Add header comment
        header = """# Generated by koci - Universal CI Definition Tool
# https://github.com/kodo/koci
#
# This Concourse CI pipeline was converted from a koci pipeline.
# Edit the original koci.yml and re-export to update.
#
# Deploy with:
#   fly -t <target> set-pipeline -p <pipeline-name> -c pipeline.yml
"""

        # Add secrets instructions if detected
        if self.detected_secrets:
            header += "#\n"
            header += "# ⚠️  VARIABLES REQUIRED - Configure in Vault or credentials file:\n"
            header += "#\n"
            for secret_name, secret_info in self.detected_secrets.items():
                header += f"#     - {secret_name.lower()}: {secret_info.description}\n"
            header += "#\n"

        # Add standard pipeline variables
        header += """#
# Required pipeline variables (set via -v or credentials file):
#   - git_url: Repository URL
#   - git_branch: Branch to track (default: main)
#   - artifacts_bucket: S3 bucket for artifacts (optional)
#
"""

        header += "\n"
        return header + output

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

        lines = [
            "",
            "╔══════════════════════════════════════════════════════════════════╗",
            "║  🔐 CONCOURSE VARIABLES REQUIRED                                 ║",
            "╚══════════════════════════════════════════════════════════════════╝",
            "",
            "The following variables need to be configured for Concourse:",
            "",
        ]

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

        lines.extend([
            "Setup Options:",
            "",
            "Option 1: Vault Integration",
            "  1. Configure Concourse to use Vault",
            "  2. Add secrets to Vault:",
            "     vault kv put concourse/<team>/<pipeline>/<var> value=<secret>",
            "",
            "Option 2: Credentials File",
            "  1. Create a credentials.yml file:",
            "     ---",
        ])

        for secret_name in self.detected_secrets.keys():
            lines.append(f"     {secret_name.lower()}: <value>")

        lines.extend([
            "",
            "  2. Deploy with:",
            "     fly set-pipeline -p <name> -c pipeline.yml -l credentials.yml",
            "",
            "Option 3: Command Line",
            "  fly set-pipeline -p <name> -c pipeline.yml \\",
        ])

        for secret_name in self.detected_secrets.keys():
            lines.append(f"    -v {secret_name.lower()}=<value> \\")

        lines.append("")

        return '\n'.join(lines)
