"""
koci Base Exporter

Abstract base class for CI platform exporters.
"""

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
import os
import re

import yaml

from ..core.models import Pipeline
import copy


# Patterns that indicate a secret environment variable
SECRET_PATTERNS = [
    r'.*_PAT$',          # Personal Access Token
    r'.*_TOKEN$',        # Token
    r'.*_KEY$',          # API Key
    r'.*_SECRET$',       # Secret
    r'.*_PASSWORD$',     # Password
    r'.*_PASS$',         # Password
    r'.*_CREDENTIAL$',   # Credential
    r'.*_API_KEY$',      # API Key
    r'^PAT$',            # PAT
    r'^TOKEN$',          # Token
    r'^SECRET$',         # Secret
    r'^AWS_.*',          # AWS credentials
    r'^DOCKER_.*',       # Docker credentials
    r'^GITHUB_TOKEN$',   # GitHub token
    r'^NPM_TOKEN$',      # NPM token
    r'^VSCE_PAT$',       # VS Code Extension PAT
    r'^OVSX_PAT$',       # Open VSX PAT
]


@dataclass
class SecretInfo:
    """Information about a secret used in the pipeline."""
    name: str
    description: str = ""
    required_by: list[str] = field(default_factory=list)


@dataclass
class ExportResult:
    """Result of an export operation."""
    content: str
    secrets: list[SecretInfo] = field(default_factory=list)
    warnings: list[str] = field(default_factory=list)


class BaseExporter(ABC):
    """
    Base class for CI platform exporters.

    All exporters must implement the export() method.
    """

    # Platform identifier
    platform: str = "base"

    # File extension for output
    extension: str = ".yml"

    # Default output filename
    default_filename: str = "pipeline.yml"

    # Secrets detected during export
    detected_secrets: dict[str, SecretInfo]

    def __init__(self):
        """Initialize exporter."""
        self.detected_secrets = {}

    @abstractmethod
    def export(self, pipeline: Pipeline) -> str:
        """
        Export pipeline to platform format.

        Args:
            pipeline: Pipeline to export

        Returns:
            String content in target format
        """
        pass

    def export_with_secrets(self, pipeline: Pipeline, secrets_file: Optional[str] = None) -> ExportResult:
        """
        Export pipeline and detect secrets.

        Args:
            pipeline: Pipeline to export
            secrets_file: Optional path to .koci.secrets.yml

        Returns:
            ExportResult with content and detected secrets
        """
        # Reset detected secrets
        self.detected_secrets = {}

        # Load secrets from file if provided
        known_secrets = self._load_secrets_file(secrets_file)

        # Detect secrets in pipeline
        self._detect_secrets_in_pipeline(pipeline, known_secrets)

        # Export content
        content = self.export(pipeline)

        # Build result
        return ExportResult(
            content=content,
            secrets=list(self.detected_secrets.values()),
            warnings=self._generate_warnings(),
        )

    def _load_secrets_file(self, secrets_file: Optional[str]) -> set[str]:
        """Load secrets from .koci.secrets.yml file."""
        secrets = set()

        # Try to find secrets file
        if secrets_file and os.path.exists(secrets_file):
            path = Path(secrets_file)
        else:
            # Look for .koci.secrets.yml in current directory
            for name in ['.koci.secrets.yml', '.koci.secrets.yaml']:
                if os.path.exists(name):
                    path = Path(name)
                    break
            else:
                return secrets

        try:
            with open(path, 'r') as f:
                data = yaml.safe_load(f)
                if isinstance(data, dict):
                    secrets = set(data.keys())
        except Exception:
            pass

        return secrets

    def _detect_secrets_in_pipeline(self, pipeline: Pipeline, known_secrets: set[str]):
        """Detect secrets used in the pipeline."""
        # Check pipeline-level env
        for key, value in pipeline.env.items():
            # If value references another secret, register the reference, not the key
            if self._value_references_secret(value, known_secrets):
                self._check_value_for_secrets(value, "pipeline environment", known_secrets)
            elif self._is_secret(key) or key in known_secrets:
                self._register_secret(key, "pipeline environment")

        # Check each stage and job
        for stage in pipeline.stages:
            for job in stage.jobs:
                job_context = f"job '{job.name}'"

                # Check job env
                for key, value in job.env.items():
                    # If value references another secret, register the reference, not the key
                    if self._value_references_secret(value, known_secrets):
                        self._check_value_for_secrets(value, job_context, known_secrets)
                    elif self._is_secret(key) or key in known_secrets:
                        self._register_secret(key, job_context)

                # Check step env
                for step in job.steps:
                    step_context = f"step '{step.name}' in {job_context}"
                    for key, value in step.env.items():
                        if self._value_references_secret(value, known_secrets):
                            self._check_value_for_secrets(value, step_context, known_secrets)
                        elif self._is_secret(key) or key in known_secrets:
                            self._register_secret(key, step_context)

                    # Check for env var references in run commands
                    for match in re.finditer(r'\$\{?([A-Z_][A-Z0-9_]*)\}?', step.run):
                        var_name = match.group(1)
                        if self._is_secret(var_name) or var_name in known_secrets:
                            self._register_secret(var_name, step_context)

    def _value_references_secret(self, value: str, known_secrets: set[str]) -> bool:
        """Check if a value is a reference to a secret like ${SECRET_NAME}."""
        match = re.match(r'^\$\{([A-Z_][A-Z0-9_]*)\}$', str(value))
        if match:
            var_name = match.group(1)
            return self._is_secret(var_name) or var_name in known_secrets
        return False

    def _check_value_for_secrets(self, value: str, context: str, known_secrets: set[str]):
        """Check if a value contains secret references like ${SECRET_NAME}."""
        for match in re.finditer(r'\$\{([A-Z_][A-Z0-9_]*)\}', str(value)):
            var_name = match.group(1)
            if self._is_secret(var_name) or var_name in known_secrets:
                self._register_secret(var_name, context)

    def _is_secret(self, name: str) -> bool:
        """Check if environment variable name looks like a secret."""
        name_upper = name.upper()
        for pattern in SECRET_PATTERNS:
            if re.match(pattern, name_upper):
                return True
        return False

    def _register_secret(self, name: str, context: str):
        """Register a detected secret."""
        if name in self.detected_secrets:
            if context not in self.detected_secrets[name].required_by:
                self.detected_secrets[name].required_by.append(context)
        else:
            self.detected_secrets[name] = SecretInfo(
                name=name,
                description=self._get_secret_description(name),
                required_by=[context],
            )

    def _get_secret_description(self, name: str) -> str:
        """Get a description for a secret based on its name."""
        descriptions = {
            'VSCE_PAT': 'VS Code Marketplace Personal Access Token (Azure DevOps)',
            'OVSX_PAT': 'Open VSX Registry Personal Access Token',
            'GITHUB_TOKEN': 'GitHub Personal Access Token',
            'NPM_TOKEN': 'npm Registry Authentication Token',
            'DOCKER_PASSWORD': 'Docker Hub Password or Access Token',
            'AWS_ACCESS_KEY_ID': 'AWS Access Key ID',
            'AWS_SECRET_ACCESS_KEY': 'AWS Secret Access Key',
        }
        return descriptions.get(name, f'Secret value for {name}')

    def _generate_warnings(self) -> list[str]:
        """Generate warnings about secrets configuration."""
        warnings = []
        if self.detected_secrets:
            warnings.append(
                f"Found {len(self.detected_secrets)} secret(s) that need to be configured in {self.platform}."
            )
        return warnings

    def export_to_file(self, pipeline: Pipeline, filepath: str):
        """
        Export pipeline to a file.

        Args:
            pipeline: Pipeline to export
            filepath: Output file path
        """
        content = self.export(pipeline)
        with open(filepath, 'w') as f:
            f.write(content)

    def get_secrets_setup_instructions(self) -> str:
        """Get platform-specific instructions for setting up secrets."""
        return "Please configure the detected secrets in your CI provider."

    def _sanitize_name(self, name: str) -> str:
        """Sanitize a name for use in the target platform."""
        # Replace spaces and special chars with hyphens
        return name.replace(' ', '-').replace('_', '-').lower()

    def _indent(self, text: str, spaces: int = 2) -> str:
        """Indent all lines of text."""
        prefix = ' ' * spaces
        return '\n'.join(prefix + line if line.strip() else line for line in text.split('\n'))

    def _convert_condition(self, when: Optional[str], condition: Optional[str]) -> Optional[str]:
        """
        Convert koci condition to platform-specific format.

        Override in subclasses for platform-specific syntax.
        """
        return condition or when

    def _apply_defaults(self, pipeline: Pipeline) -> Pipeline:
        """
        Apply pipeline defaults to all jobs.

        Creates a deep copy of the pipeline with defaults merged into jobs.
        Job-level values take precedence over defaults.

        Args:
            pipeline: Original pipeline

        Returns:
            New pipeline with defaults applied
        """
        if not pipeline.defaults:
            return pipeline

        # Create a deep copy to avoid modifying original
        new_pipeline = copy.deepcopy(pipeline)
        defaults = new_pipeline.defaults

        default_image = defaults.get('image')
        default_working_dir = defaults.get('working_directory')
        default_timeout = defaults.get('timeout_minutes')

        for stage in new_pipeline.stages:
            for job in stage.jobs:
                # Apply default image if job doesn't have one
                if default_image and (not job.image or job.image == 'alpine:latest'):
                    job.image = default_image

                # Apply default working_directory if job uses default
                if default_working_dir and job.working_directory == '/workspace':
                    job.working_directory = default_working_dir

                # Apply default timeout if job uses default
                if default_timeout and job.timeout_minutes == 60:
                    job.timeout_minutes = default_timeout

        return new_pipeline

    def _get_triggers_for_platform(self, triggers: dict) -> dict:
        """
        Get trigger configuration for the platform.

        Override in subclasses for platform-specific trigger syntax.

        Args:
            triggers: koci 'on' configuration

        Returns:
            Platform-specific trigger configuration
        """
        return triggers or {}
