"""
koci GitHub Actions Exporter

Exports koci pipelines to GitHub Actions workflow format.
"""

import yaml
from typing import Any, Optional

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


class GitHubActionsExporter(BaseExporter):
    """
    Exporter for GitHub Actions workflows.

    Converts koci pipeline to .github/workflows/*.yml format.
    """

    platform = "github-actions"
    extension = ".yml"
    default_filename = "ci.yml"

    def __init__(self, runs_on: str = "ubuntu-latest"):
        """
        Initialize exporter.

        Args:
            runs_on: Default runner to use
        """
        super().__init__()
        self.runs_on = runs_on

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

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

        workflow = {
            'name': pipeline.name,
        }

        # Triggers
        workflow['on'] = self._convert_triggers(pipeline.on)

        # Environment variables - separate regular env from secrets
        env_vars = {}
        for key, value in pipeline.env.items():
            if self._is_secret(key) or key in self.detected_secrets:
                # Use GitHub Actions secrets syntax
                env_vars[key] = f'${{{{ secrets.{key} }}}}'
            else:
                env_vars[key] = value

        # Add detected secrets that reference env vars
        for secret_name in self.detected_secrets.keys():
            if secret_name not in env_vars:
                env_vars[secret_name] = f'${{{{ secrets.{secret_name} }}}}'

        if env_vars:
            workflow['env'] = env_vars

        # Jobs
        workflow['jobs'] = {}

        for stage in pipeline.stages:
            for job in stage.jobs:
                gh_job = self._convert_job(job, stage.services, pipeline)
                workflow['jobs'][self._sanitize_name(job.name)] = gh_job

        # Generate YAML with nice formatting
        return self._generate_yaml(workflow)

    def _convert_triggers(self, triggers: dict) -> dict:
        """Convert koci triggers to GitHub Actions format."""
        if not triggers:
            return {
                'push': {'branches': ['main']},
                'pull_request': {'branches': ['main']},
            }

        result = {}

        if 'push' in triggers:
            push = triggers['push']
            if push is None:
                result['push'] = None
            else:
                result['push'] = {}
                if 'branches' in push:
                    result['push']['branches'] = push['branches']
                if 'tags' in push:
                    result['push']['tags'] = push['tags']

        if 'pull_request' in triggers:
            pr = triggers['pull_request']
            if pr is None:
                result['pull_request'] = None
            else:
                result['pull_request'] = {}
                if 'branches' in pr:
                    result['pull_request']['branches'] = pr['branches']

        if 'schedule' in triggers:
            result['schedule'] = triggers['schedule']

        if 'workflow_dispatch' in triggers:
            result['workflow_dispatch'] = triggers['workflow_dispatch']

        return result

    def _convert_job(self, job: Job, stage_services: list[Service], pipeline: Pipeline) -> dict:
        """Convert a koci job to GitHub Actions job."""
        gh_job: dict[str, Any] = {
            'runs-on': self.runs_on,
        }

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

        # Condition
        condition = self._convert_job_condition(job)
        if condition:
            gh_job['if'] = condition

        # Container with working directory
        gh_job['container'] = {
            'image': job.image,
        }
        if job.working_directory and job.working_directory != '/workspace':
            gh_job['container']['options'] = f'--workdir {job.working_directory}'

        # Job-level environment
        job_env = {**pipeline.env, **job.env}
        if job_env:
            gh_job['env'] = job_env

        # Services
        if stage_services:
            gh_job['services'] = {}
            for svc in stage_services:
                gh_job['services'][svc.name] = self._convert_service(svc)

        # Matrix
        if job.matrix:
            gh_job['strategy'] = {
                'matrix': job.matrix.variables,
            }
            if job.matrix.include:
                gh_job['strategy']['matrix']['include'] = job.matrix.include
            if job.matrix.exclude:
                gh_job['strategy']['matrix']['exclude'] = job.matrix.exclude

        # Timeout
        if job.timeout_minutes != 60:
            gh_job['timeout-minutes'] = job.timeout_minutes

        # Continue on error
        if job.continue_on_error:
            gh_job['continue-on-error'] = True

        # Retry (GitHub Actions doesn't have native job retry, use strategy)
        if job.retry > 0:
            # Add fail-fast: false to allow retries via re-run
            if 'strategy' not in gh_job:
                gh_job['strategy'] = {}
            gh_job['strategy']['fail-fast'] = False
            # Note: True retry requires manual re-run or reusable workflow

        # Steps
        gh_job['steps'] = []

        # Add checkout step
        gh_job['steps'].append({
            'uses': 'actions/checkout@v4',
        })

        # Download artifacts if consuming
        if job.artifacts and job.artifacts.consume:
            for consume_from in job.artifacts.consume:
                gh_job['steps'].append({
                    'name': f'Download artifacts from {consume_from}',
                    'uses': 'actions/download-artifact@v4',
                    'with': {
                        'name': f'{self._sanitize_name(consume_from)}-artifacts',
                    },
                })

        # Convert steps
        for step in job.steps:
            gh_job['steps'].append(self._convert_step(step))

        # Upload artifacts if producing
        if job.artifacts and job.artifacts.paths:
            for path in job.artifacts.paths:
                gh_job['steps'].append({
                    'name': f'Upload artifact: {path}',
                    'uses': 'actions/upload-artifact@v4',
                    'with': {
                        'name': f'{self._sanitize_name(job.name)}-artifacts',
                        'path': path,
                        'retention-days': job.artifacts.retention_days,
                    },
                })

        # Test reports
        if job.reports:
            # JUnit test reports using dorny/test-reporter
            if job.reports.junit:
                gh_job['steps'].append({
                    'name': 'Publish Test Results',
                    'uses': 'dorny/test-reporter@v1',
                    'if': 'always()',
                    'with': {
                        'name': f'{self._sanitize_name(job.name)} Test Results',
                        'path': job.reports.junit,
                        'reporter': 'java-junit',
                    },
                })

            # Coverage reports using codecov
            if job.reports.coverage:
                coverage_step = {
                    'name': 'Upload Coverage',
                    'uses': 'codecov/codecov-action@v4',
                    'if': 'always()',
                    'with': {
                        'files': job.reports.coverage.path,
                    },
                }
                # Add threshold as fail_ci if specified
                if job.reports.coverage.threshold:
                    coverage_step['with']['fail_ci_if_error'] = True
                gh_job['steps'].append(coverage_step)

        return gh_job

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

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

        gh_step['run'] = step.run

        if step.working_directory:
            gh_step['working-directory'] = step.working_directory

        if step.env:
            gh_step['env'] = step.env

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

        if step.continue_on_error:
            gh_step['continue-on-error'] = True

        if step.timeout_minutes:
            gh_step['timeout-minutes'] = step.timeout_minutes

        return gh_step

    def _convert_service(self, service: Service) -> dict:
        """Convert a koci service to GitHub Actions service."""
        gh_service: dict[str, Any] = {
            'image': service.image,
        }

        if service.env:
            gh_service['env'] = service.env

        if service.ports:
            gh_service['ports'] = service.ports

        if service.volumes:
            gh_service['volumes'] = service.volumes

        if service.options:
            gh_service['options'] = ' '.join(service.options)

        return gh_service

    def _convert_job_condition(self, job: Job) -> Optional[str]:
        """Convert job condition to GitHub Actions format."""
        conditions = []

        if job.when:
            # Convert simple when conditions
            gh_condition = self._convert_when(job.when)
            if gh_condition:
                conditions.append(gh_condition)

        if job.condition:
            # Pass through if expressions (already in ${{ }} format)
            cond = job.condition.strip()
            if cond.startswith('${{') and cond.endswith('}}'):
                # Extract inner expression
                inner = cond[3:-2].strip()
                conditions.append(inner)
            else:
                conditions.append(cond)

        if not conditions:
            return None

        if len(conditions) == 1:
            return conditions[0]

        return ' && '.join(f'({c})' for c in conditions)

    def _convert_when(self, when: str) -> Optional[str]:
        """Convert simple when condition to GitHub Actions expression."""
        when = when.strip()

        # branch == 'main' -> github.ref == 'refs/heads/main'
        if 'branch ==' in when:
            import re
            match = re.search(r"branch\s*==\s*['\"](.+?)['\"]", when)
            if match:
                branch = match.group(1)
                return f"github.ref == 'refs/heads/{branch}'"

        # branch != 'main'
        if 'branch !=' in when:
            import re
            match = re.search(r"branch\s*!=\s*['\"](.+?)['\"]", when)
            if match:
                branch = match.group(1)
                return f"github.ref != 'refs/heads/{branch}'"

        # tag =~ 'v*'
        if 'tag =~' in when:
            import re
            match = re.search(r"tag\s*=~\s*['\"](.+?)['\"]", when)
            if match:
                pattern = match.group(1)
                prefix = pattern.rstrip('*')
                return f"startsWith(github.ref, 'refs/tags/{prefix}')"

        # always
        if when.lower() == 'always':
            return 'always()'

        # never
        if when.lower() == 'never':
            return 'false'

        return 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 GitHub Actions workflow 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 GitHub:\n"
            header += "#     Repository → Settings → Secrets and variables → Actions\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 GitHub Actions-specific instructions for setting up secrets."""
        if not self.detected_secrets:
            return ""

        lines = [
            "",
            "╔══════════════════════════════════════════════════════════════════╗",
            "║  🔐 GITHUB ACTIONS SECRETS CONFIGURATION REQUIRED                ║",
            "╚══════════════════════════════════════════════════════════════════╝",
            "",
            "The following secrets need to be configured in GitHub:",
            "",
        ]

        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 repository on GitHub",
            "  2. Navigate to: Settings → Secrets and variables → Actions",
            "  3. Click 'New repository secret'",
            "  4. For each secret above:",
            "     - Name: <secret name exactly as shown above>",
            "     - Secret: <your secret value>",
            "",
            "For organization-wide secrets:",
            "  - Go to: Organization → Settings → Secrets and variables → Actions",
            "",
        ])

        return '\n'.join(lines)
