"""
koci GitLab CI Exporter

Exports koci pipelines to GitLab CI format (.gitlab-ci.yml).
"""

import yaml
from typing import Any, Optional

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


class GitLabExporter(BaseExporter):
    """
    Exporter for GitLab CI pipelines.

    Generates .gitlab-ci.yml format.
    """

    platform = "gitlab"
    extension = ".yml"
    default_filename = ".gitlab-ci.yml"

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

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

        gitlab_config: dict[str, Any] = {}

        # Workflow rules (triggers)
        if pipeline.on:
            gitlab_config['workflow'] = self._convert_triggers(pipeline.on)

        # Stages
        gitlab_config['stages'] = [s.name for s in pipeline.stages]

        # Global variables - separate regular vars from secrets
        variables = {}
        for key, value in pipeline.env.items():
            if self._is_secret(key) or key in self.detected_secrets:
                # Secret variables reference CI/CD variables
                # They'll be configured in GitLab UI, so we just document them
                variables[key] = f'${{CI_SECRET_{key}}}'
            else:
                variables[key] = value

        if variables:
            gitlab_config['variables'] = variables

        # Default settings
        gitlab_config['default'] = {
            'interruptible': True,
        }

        # Jobs
        for stage in pipeline.stages:
            for job in stage.jobs:
                job_config = self._convert_job(job, stage.services, pipeline)
                gitlab_config[self._sanitize_name(job.name)] = job_config

        # Generate YAML
        return self._generate_yaml(gitlab_config)

    def _convert_job(self, job: Job, stage_services: list[Service], pipeline: Pipeline) -> dict:
        """Convert a koci job to GitLab CI job."""
        gitlab_job: dict[str, Any] = {
            'stage': job.stage,
            'image': job.image,
        }

        # Working directory - add before_script to cd into it
        if job.working_directory and job.working_directory != '/workspace':
            gitlab_job['before_script'] = [
                f'mkdir -p {job.working_directory}',
                f'cd {job.working_directory}',
            ]

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

        # Services (stage-level only, jobs don't have services)
        all_services = list(stage_services)

        if all_services:
            gitlab_job['services'] = []
            for svc in all_services:
                gitlab_job['services'].append(self._convert_service(svc))

        # Variables (environment)
        job_env = {**pipeline.env, **job.env}
        if job_env:
            gitlab_job['variables'] = job_env

        # Matrix (parallel) with include/exclude support
        if job.matrix:
            matrix_config = []

            # Add main variables
            matrix_config.append(job.matrix.variables)

            # Handle include - add extra combinations
            if job.matrix.include:
                for inc in job.matrix.include:
                    matrix_config.append(inc)

            gitlab_job['parallel'] = {
                'matrix': matrix_config
            }

            # Handle exclude via rules (GitLab doesn't have native exclude)
            if job.matrix.exclude:
                exclude_rules = self._generate_matrix_exclude_rules(job.matrix.exclude)
                if 'rules' not in gitlab_job:
                    gitlab_job['rules'] = []
                gitlab_job['rules'].extend(exclude_rules)

        # Rules (conditions)
        rules = self._convert_rules(job)
        if rules:
            gitlab_job['rules'] = rules

        # Script (steps)
        script = []
        for step in job.steps:
            if step.name:
                script.append(f'echo "=== {step.name} ==="')

            if '\n' in step.run:
                script.extend(step.run.strip().split('\n'))
            else:
                script.append(step.run)

        gitlab_job['script'] = script

        # Artifacts
        if job.artifacts:
            if job.artifacts.paths:
                gitlab_job['artifacts'] = {
                    'paths': job.artifacts.paths,
                    'expire_in': f"{job.artifacts.retention_days} days",
                }

        # Test reports - add to artifacts.reports
        if job.reports:
            if 'artifacts' not in gitlab_job:
                gitlab_job['artifacts'] = {}

            if 'reports' not in gitlab_job['artifacts']:
                gitlab_job['artifacts']['reports'] = {}

            # JUnit reports
            if job.reports.junit:
                gitlab_job['artifacts']['reports']['junit'] = job.reports.junit

            # Coverage reports
            if job.reports.coverage:
                cov = job.reports.coverage
                cov_format = cov.format if cov.format != 'auto' else 'cobertura'
                if cov_format == 'cobertura':
                    gitlab_job['artifacts']['reports']['coverage_report'] = {
                        'coverage_format': 'cobertura',
                        'path': cov.path,
                    }

        # Artifact consumption
        if job.artifacts and job.artifacts.consume:
            # Dependencies for artifact passing
            if 'needs' not in gitlab_job:
                gitlab_job['needs'] = []
            for consume_from in job.artifacts.consume:
                dep = self._sanitize_name(consume_from)
                if dep not in gitlab_job['needs']:
                    gitlab_job['needs'].append(dep)

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

        # Allow failure
        if job.continue_on_error:
            gitlab_job['allow_failure'] = True

        # Retry
        if job.retry > 0:
            gitlab_job['retry'] = job.retry

        return gitlab_job

    def _convert_service(self, service: Service) -> dict:
        """Convert a koci service to GitLab CI service."""
        gitlab_service: dict[str, Any] = {
            'name': service.image,
        }

        if service.name:
            gitlab_service['alias'] = service.name

        if service.env:
            gitlab_service['variables'] = service.env

        # Add entrypoint/command if healthcheck defined (for service readiness)
        if hasattr(service, 'healthcheck') and service.healthcheck:
            # GitLab uses the alias for networking, ports are implicit
            pass

        return gitlab_service

    def _convert_rules(self, job: Job) -> Optional[list[dict]]:
        """Convert job conditions to GitLab CI rules."""
        if not job.when and not job.condition:
            return None

        rules = []

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

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

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

            # tag =~ 'v*'
            elif 'tag =~' in when:
                import re
                match = re.search(r"tag\s*=~\s*['\"](.+?)['\"]", when)
                if match:
                    pattern = match.group(1)
                    # Convert glob to regex
                    regex = pattern.replace('*', '.*')
                    rules.append({
                        'if': f"$CI_COMMIT_TAG =~ /^{regex}$/",
                    })

            # always
            elif when.lower() == 'always':
                rules.append({'when': 'always'})

            # never
            elif when.lower() == 'never':
                rules.append({'when': 'never'})

        if job.condition:
            # Pass through complex conditions
            cond = job.condition.strip()
            if cond.startswith('${{'):
                # Extract and convert
                inner = cond[3:-2].strip() if cond.endswith('}}') else cond
                # Basic conversion - may need enhancement
                inner = inner.replace('success()', '1 == 1')
                inner = inner.replace('failure()', '$CI_JOB_STATUS == "failed"')
                rules.append({'if': inner})
            else:
                rules.append({'if': cond})

        # Add default rule if we have rules
        if rules and not any('when' in r and r.get('when') == 'never' for r in rules):
            rules.append({'when': 'on_success'})

        return rules if rules else None

    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 GitLab CI 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 GitLab:\n"
            header += "#     Project → Settings → CI/CD → 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 GitLab-specific instructions for setting up secrets."""
        if not self.detected_secrets:
            return ""

        lines = [
            "",
            "╔══════════════════════════════════════════════════════════════════╗",
            "║  🔐 GITLAB CI/CD VARIABLES CONFIGURATION REQUIRED                ║",
            "╚══════════════════════════════════════════════════════════════════╝",
            "",
            "The following variables need to be configured in GitLab:",
            "",
        ]

        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 GitLab",
            "  2. Navigate to: Settings → CI/CD → Variables",
            "  3. Click 'Add variable'",
            "  4. For each variable above:",
            "     - Key: <variable name exactly as shown above>",
            "     - Value: <your secret value>",
            "     - Flags:",
            "       ☑ Protect variable (only available in protected branches/tags)",
            "       ☑ Mask variable (hide in job logs)",
            "",
            "For group-level variables:",
            "  - Go to: Group → Settings → CI/CD → Variables",
            "",
        ])

        return '\n'.join(lines)

    def _convert_triggers(self, triggers: dict) -> dict:
        """Convert koci triggers to GitLab workflow:rules."""
        rules = []

        # Handle push trigger
        if 'push' in triggers:
            push = triggers['push']
            if isinstance(push, dict):
                branches = push.get('branches', [])
                for branch in branches:
                    if branch == '*':
                        rules.append({'if': '$CI_COMMIT_BRANCH'})
                    else:
                        rules.append({'if': f"$CI_COMMIT_BRANCH == '{branch}'"})
                tags = push.get('tags', [])
                for tag in tags:
                    if '*' in tag:
                        # Convert glob to regex
                        regex = tag.replace('*', '.*')
                        rules.append({'if': f"$CI_COMMIT_TAG =~ /^{regex}$/"})
                    else:
                        rules.append({'if': f"$CI_COMMIT_TAG == '{tag}'"})
            elif push is True or push == 'true':
                rules.append({'if': '$CI_COMMIT_BRANCH'})
                rules.append({'if': '$CI_COMMIT_TAG'})

        # Handle pull_request/merge_request trigger
        if 'pull_request' in triggers or 'merge_request' in triggers:
            pr = triggers.get('pull_request') or triggers.get('merge_request')
            if isinstance(pr, dict):
                branches = pr.get('branches', [])
                for branch in branches:
                    rules.append({'if': f"$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == '{branch}'"})
            else:
                rules.append({'if': '$CI_MERGE_REQUEST_ID'})

        # Handle schedule trigger
        if 'schedule' in triggers:
            rules.append({'if': '$CI_PIPELINE_SOURCE == "schedule"'})

        # Handle manual trigger
        if 'manual' in triggers:
            rules.append({'if': '$CI_PIPELINE_SOURCE == "web"'})

        if not rules:
            rules.append({'when': 'always'})

        return {'rules': rules}

    def _generate_matrix_exclude_rules(self, excludes: list[dict]) -> list[dict]:
        """Generate GitLab rules to exclude matrix combinations."""
        rules = []

        for exclude in excludes:
            # Build condition that matches the exclusion
            conditions = []
            for var, val in exclude.items():
                conditions.append(f'${{{var.upper()}}} == "{val}"')

            if conditions:
                # Exclude when ALL conditions match
                combined = ' && '.join(conditions)
                rules.append({
                    'if': combined,
                    'when': 'never',
                })

        return rules
