"""
koci CircleCI Exporter

Exports koci pipelines to CircleCI format (.circleci/config.yml).
"""

import re
import yaml
from typing import Any, Optional

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


class CircleCIExporter(BaseExporter):
    """
    Exporter for CircleCI pipelines.

    Generates .circleci/config.yml format.
    """

    platform = "circleci"
    extension = ".yml"
    default_filename = ".circleci/config.yml"

    def __init__(self, version: str = "2.1"):
        """
        Initialize exporter.

        Args:
            version: CircleCI config version
        """
        super().__init__()
        self.version = version

    def export(self, pipeline: Pipeline) -> str:
        """Export pipeline to CircleCI 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] = {
            'version': self.version,
        }

        # Collect all unique images to create executors
        executors = self._collect_executors(pipeline)
        if executors:
            config['executors'] = executors

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

        # Workflows
        config['workflows'] = self._generate_workflows(pipeline)

        # Generate YAML
        return self._generate_yaml(config)

    def _collect_executors(self, pipeline: Pipeline) -> dict[str, Any]:
        """Collect unique Docker images as reusable executors."""
        executors = {}
        seen_images = set()

        for stage in pipeline.stages:
            for job in stage.jobs:
                if job.image and job.image not in seen_images:
                    seen_images.add(job.image)
                    executor_name = self._image_to_executor_name(job.image)
                    executors[executor_name] = {
                        'docker': [{'image': job.image}],
                        'working_directory': job.working_directory or '/workspace',
                    }

        return executors

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

        # Executor (Docker image)
        executor_name = self._image_to_executor_name(job.image)
        circleci_job['executor'] = executor_name

        # Working directory override
        if job.working_directory and job.working_directory != '/workspace':
            circleci_job['working_directory'] = job.working_directory

        # Environment variables
        job_env = {**pipeline.env, **job.env}
        if job_env:
            circleci_job['environment'] = job_env

        # Services as additional Docker images
        all_services = list(stage_services)

        if all_services:
            # CircleCI uses multiple docker images in the executor
            docker_images = [{'image': job.image}]
            for svc in all_services:
                svc_config: dict[str, Any] = {'image': svc.image}
                if svc.env:
                    svc_config['environment'] = svc.env
                docker_images.append(svc_config)

            # Override executor with inline docker config
            del circleci_job['executor']
            circleci_job['docker'] = docker_images

        # Resource class (optional, based on timeout as hint)
        if job.timeout_minutes > 60:
            circleci_job['resource_class'] = 'large'

        # Steps
        steps = []

        # Checkout code
        steps.append('checkout')

        # Attach workspace if consuming artifacts
        if job.artifacts and job.artifacts.consume:
            steps.append({
                'attach_workspace': {
                    'at': '.',
                }
            })

        # Main steps
        for step in job.steps:
            step_config = self._convert_step(step)
            steps.append(step_config)

        # Store artifacts
        if job.artifacts and job.artifacts.paths:
            for path in job.artifacts.paths:
                steps.append({
                    'store_artifacts': {
                        'path': path,
                    }
                })
            # Also persist to workspace for downstream jobs
            steps.append({
                'persist_to_workspace': {
                    'root': '.',
                    'paths': job.artifacts.paths,
                }
            })

        # Test reports
        if job.reports:
            if job.reports.junit:
                steps.append({
                    'store_test_results': {
                        'path': job.reports.junit.rsplit('/', 1)[0] if '/' in job.reports.junit else '.',
                    }
                })

        circleci_job['steps'] = steps

        return circleci_job

    def _convert_step(self, step: Step) -> dict:
        """Convert a koci step to CircleCI step."""
        step_config: dict[str, Any] = {}

        run_config: dict[str, Any] = {
            'command': step.run,
        }

        if step.name:
            run_config['name'] = step.name

        if step.working_directory:
            run_config['working_directory'] = step.working_directory

        if step.env:
            run_config['environment'] = step.env

        if step.shell:
            run_config['shell'] = step.shell

        if step.timeout_minutes:
            # CircleCI uses no_output_timeout in seconds
            run_config['no_output_timeout'] = f"{step.timeout_minutes}m"

        if step.continue_on_error:
            run_config['when'] = 'always'

        step_config['run'] = run_config

        return step_config

    def _generate_workflows(self, pipeline: Pipeline) -> dict[str, Any]:
        """Generate CircleCI workflows from pipeline stages."""
        workflows: dict[str, Any] = {
            pipeline.name or 'main': {
                'jobs': [],
            }
        }

        workflow_jobs = workflows[pipeline.name or 'main']['jobs']

        # Build job list with dependencies
        for stage in pipeline.stages:
            for job in stage.jobs:
                job_entry: dict[str, Any] = {}
                job_name = self._sanitize_job_name(job.name)

                # Dependencies
                requires = []
                if job.needs:
                    requires = [self._sanitize_job_name(n) for n in job.needs]

                # Matrix builds
                if job.matrix:
                    job_entry[job_name] = {
                        'matrix': {
                            'parameters': job.matrix.variables,
                        }
                    }
                    if requires:
                        job_entry[job_name]['requires'] = requires

                    # Handle exclude
                    if job.matrix.exclude:
                        job_entry[job_name]['matrix']['exclude'] = job.matrix.exclude
                else:
                    if requires:
                        job_entry[job_name] = {'requires': requires}
                    else:
                        job_entry = job_name

                # Filters (conditions)
                filters = self._convert_conditions(job)
                if filters:
                    if isinstance(job_entry, str):
                        job_entry = {job_name: {'filters': filters}}
                    else:
                        if job_name in job_entry:
                            job_entry[job_name]['filters'] = filters
                        else:
                            job_entry['filters'] = filters

                workflow_jobs.append(job_entry)

        # Add triggers if defined
        if pipeline.on:
            workflow_triggers = self._convert_triggers(pipeline.on)
            if workflow_triggers:
                workflows[pipeline.name or 'main'].update(workflow_triggers)

        return workflows

    def _convert_conditions(self, job: Job) -> Optional[dict]:
        """Convert job conditions to CircleCI filters."""
        if not job.when and not job.condition:
            return None

        filters: dict[str, Any] = {}

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

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

            # branch != 'main'
            elif 'branch !=' in when:
                match = re.search(r"branch\s*!=\s*['\"](.+?)['\"]", when)
                if match:
                    filters['branches'] = {'ignore': [match.group(1)]}

            # tag =~ 'v*'
            elif 'tag =~' in when:
                match = re.search(r"tag\s*=~\s*['\"](.+?)['\"]", when)
                if match:
                    pattern = match.group(1)
                    # Convert glob to regex
                    regex = '/^' + pattern.replace('*', '.*') + '$/'
                    filters['tags'] = {'only': [regex]}
                    # Tags typically need branches ignored
                    filters['branches'] = {'ignore': ['/.*/']}

        return filters if filters else None

    def _convert_triggers(self, triggers: dict) -> dict:
        """Convert koci triggers to CircleCI workflow triggers."""
        result: dict[str, Any] = {}

        # Handle schedule trigger
        if 'schedule' in triggers:
            schedule = triggers['schedule']
            if isinstance(schedule, str):
                result['triggers'] = [{
                    'schedule': {
                        'cron': schedule,
                        'filters': {'branches': {'only': ['main']}},
                    }
                }]
            elif isinstance(schedule, list):
                schedules = []
                for s in schedule:
                    if isinstance(s, dict) and 'cron' in s:
                        schedules.append({
                            'schedule': {
                                'cron': s['cron'],
                                'filters': {'branches': {'only': ['main']}},
                            }
                        })
                if schedules:
                    result['triggers'] = schedules

        return result

    def _image_to_executor_name(self, image: str) -> str:
        """Convert Docker image name to executor name."""
        # Extract base name without tag
        name = image.split(':')[0].split('/')[-1]
        # Sanitize
        name = re.sub(r'[^a-z0-9-]', '-', name.lower())
        return name

    def _sanitize_job_name(self, name: str) -> str:
        """Sanitize job name for CircleCI."""
        # Replace spaces and special chars
        name = re.sub(r'[^a-zA-Z0-9_-]', '-', name)
        return name

    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 CircleCI configuration was converted from a koci pipeline.
# Edit the original koci.yml and re-export to update.
"""

        # Add secrets instructions if detected
        if self.detected_secrets:
            header += "#\n"
            header += "# ⚠️  SECRETS REQUIRED - Configure these in CircleCI:\n"
            header += "#     Project Settings → Environment Variables\n"
            header += "#\n"
            for secret_name, secret_info in self.detected_secrets.items():
                header += f"#     - {secret_name}: {secret_info.description}\n"
            header += "#\n"

        header += "\n"
        return header + output

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

        lines = [
            "",
            "╔══════════════════════════════════════════════════════════════════╗",
            "║  🔐 CIRCLECI ENVIRONMENT VARIABLES REQUIRED                      ║",
            "╚══════════════════════════════════════════════════════════════════╝",
            "",
            "The following environment variables need to be configured in CircleCI:",
            "",
        ]

        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 your project in CircleCI",
            "  2. Navigate to: Project Settings → Environment Variables",
            "  3. Click 'Add Environment Variable'",
            "  4. For each variable above:",
            "     - Name: <variable name exactly as shown above>",
            "     - Value: <your secret value>",
            "",
            "For organization-level secrets:",
            "  - Go to: Organization Settings → Contexts",
            "  - Create or select a context",
            "  - Add environment variables to the context",
            "  - Reference the context in your workflow",
            "",
        ])

        return '\n'.join(lines)
