"""
╔══════════════════════════════════════════════════════════════════════════════╗
║                    UnrealMate - CI/CD Pipeline Generator                     ║
║                                                                              ║
║  Author: gktrk363                                                            ║
║  GitHub: https://github.com/gktrk363/unrealmate                              ║
║  Purpose: Generate CI/CD pipelines for Unreal Engine projects                ║
╚══════════════════════════════════════════════════════════════════════════════╝

© 2026 gktrk363 - Crafted with passion for Unreal Engine developers
"""

from __future__ import annotations

import json
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum, auto
from pathlib import Path
from typing import Any, Optional

from rich.console import Console
from rich.panel import Panel
from rich.table import Table

console = Console()


# ═══════════════════════════════════════════════════════════════════════════════
# DATA STRUCTURES
# ═══════════════════════════════════════════════════════════════════════════════


class Platform(Enum):
    """Target platforms for builds."""
    WIN64 = "Win64"
    LINUX = "Linux"
    MAC = "Mac"
    ANDROID = "Android"
    IOS = "iOS"
    PS5 = "PS5"
    XBOX_SERIES = "XboxSeriesX"


class BuildConfiguration(Enum):
    """Build configurations."""
    DEVELOPMENT = "Development"
    SHIPPING = "Shipping"
    DEBUG = "Debug"
    TEST = "Test"


class CIPlatform(Enum):
    """CI/CD platforms."""
    JENKINS = auto()
    GITLAB = auto()
    GITHUB_ACTIONS = auto()
    AZURE_DEVOPS = auto()
    TEAMCITY = auto()


@dataclass
class BuildStep:
    """A single build step."""
    name: str
    command: str
    timeout_minutes: int = 60
    continue_on_error: bool = False
    condition: str = ""
    environment: dict[str, str] = field(default_factory=dict)


@dataclass
class PipelineConfig:
    """Configuration for a CI/CD pipeline."""
    project_name: str
    engine_version: str
    target_platforms: list[Platform]
    configurations: list[BuildConfiguration]
    
    # Build options
    cook_content: bool = True
    package_build: bool = True
    archive_build: bool = True
    run_tests: bool = True
    
    # Environment
    ue_root: str = "C:/Program Files/Epic Games/UE_5.4"
    build_output: str = "./Build"
    
    # Notifications
    notify_slack: bool = False
    slack_webhook: str = ""
    notify_email: bool = False
    email_recipients: list[str] = field(default_factory=list)
    
    # Advanced
    custom_build_args: str = ""
    pre_build_steps: list[BuildStep] = field(default_factory=list)
    post_build_steps: list[BuildStep] = field(default_factory=list)


# ═══════════════════════════════════════════════════════════════════════════════
# JENKINS PIPELINE GENERATOR
# ═══════════════════════════════════════════════════════════════════════════════


class JenkinsPipelineGenerator:
    """Generate Jenkins pipeline files."""
    
    def generate(self, config: PipelineConfig) -> str:
        """Generate a Jenkinsfile."""
        lines = [
            "// Generated by UnrealMate",
            f"// Project: {config.project_name}",
            "",
            "pipeline {",
            "    agent any",
            "",
            "    environment {",
            f"        UE_ROOT = '{config.ue_root}'",
            f"        PROJECT_NAME = '{config.project_name}'",
            "        BUILD_OUTPUT = '${WORKSPACE}/Build'",
            "    }",
            "",
            "    options {",
            "        timeout(time: 4, unit: 'HOURS')",
            "        buildDiscarder(logRotator(numToKeepStr: '10'))",
            "        timestamps()",
            "    }",
            "",
            "    stages {",
        ]
        
        # Checkout stage
        lines.extend([
            "        stage('Checkout') {",
            "            steps {",
            "                checkout scm",
            "            }",
            "        }",
            "",
        ])
        
        # Pre-build steps
        if config.pre_build_steps:
            lines.extend([
                "        stage('Pre-Build') {",
                "            steps {",
            ])
            for step in config.pre_build_steps:
                lines.append(f"                sh '{step.command}'")
            lines.extend([
                "            }",
                "        }",
                "",
            ])
        
        # Build stage for each platform
        for platform in config.target_platforms:
            for build_config in config.configurations:
                stage_name = f"Build {platform.value} {build_config.value}"
                lines.extend([
                    f"        stage('{stage_name}') {{",
                    "            steps {",
                    "                script {",
                    self._get_build_command(config, platform, build_config),
                    "                }",
                    "            }",
                    "        }",
                    "",
                ])
        
        # Test stage
        if config.run_tests:
            lines.extend([
                "        stage('Run Tests') {",
                "            steps {",
                "                script {",
                "                    def testCmd = \"${UE_ROOT}/Engine/Binaries/Win64/UnrealEditor-Cmd.exe\"",
                f"                    testCmd += \" ${{WORKSPACE}}/{config.project_name}.uproject\"",
                "                    testCmd += \" -ExecCmds=\\\"Automation RunTests Now\\\"\"",
                "                    testCmd += \" -Unattended -NoPause -Log\"",
                "                    bat testCmd",
                "                }",
                "            }",
                "        }",
                "",
            ])
        
        # Archive stage
        if config.archive_build:
            lines.extend([
                "        stage('Archive') {",
                "            steps {",
                "                archiveArtifacts artifacts: 'Build/**/*', fingerprint: true",
                "            }",
                "        }",
                "",
            ])
        
        # Post-build steps
        if config.post_build_steps:
            lines.extend([
                "        stage('Post-Build') {",
                "            steps {",
            ])
            for step in config.post_build_steps:
                lines.append(f"                sh '{step.command}'")
            lines.extend([
                "            }",
                "        }",
                "",
            ])
        
        lines.append("    }")  # End stages
        
        # Post section
        lines.extend([
            "",
            "    post {",
            "        always {",
            "            cleanWs()",
            "        }",
            "        success {",
        ])
        
        if config.notify_slack:
            lines.append(f"            slackSend(color: 'good', message: 'Build succeeded: ${{env.JOB_NAME}} #${{env.BUILD_NUMBER}}')")
        
        lines.extend([
            "            echo 'Build succeeded!'",
            "        }",
            "        failure {",
        ])
        
        if config.notify_slack:
            lines.append(f"            slackSend(color: 'danger', message: 'Build failed: ${{env.JOB_NAME}} #${{env.BUILD_NUMBER}}')")
        
        lines.extend([
            "            echo 'Build failed!'",
            "        }",
            "    }",
            "}",
        ])
        
        return "\n".join(lines)
    
    def _get_build_command(
        self,
        config: PipelineConfig,
        platform: Platform,
        build_config: BuildConfiguration,
    ) -> str:
        """Generate build command for a platform."""
        cmd_parts = [
            "                    def buildCmd = \"${UE_ROOT}/Engine/Build/BatchFiles/RunUAT.bat\"",
            f"                    buildCmd += \" BuildCookRun\"",
            f"                    buildCmd += \" -project=${{WORKSPACE}}/{config.project_name}.uproject\"",
            f"                    buildCmd += \" -targetplatform={platform.value}\"",
            f"                    buildCmd += \" -clientconfig={build_config.value}\"",
        ]
        
        if config.cook_content:
            cmd_parts.append("                    buildCmd += \" -cook\"")
        
        if config.package_build:
            cmd_parts.append("                    buildCmd += \" -stage -pak -package\"")
        
        if config.archive_build:
            cmd_parts.append("                    buildCmd += \" -archive -archivedirectory=${BUILD_OUTPUT}\"")
        
        cmd_parts.extend([
            "                    buildCmd += \" -build -compile\"",
            "                    buildCmd += \" -unattended -utf8output\"",
            "                    bat buildCmd",
        ])
        
        return "\n".join(cmd_parts)
    
    def save(self, config: PipelineConfig, output_path: Path) -> None:
        """Save pipeline to file."""
        content = self.generate(config)
        output_path.write_text(content, encoding="utf-8")
        console.print(f"[green]✓ Jenkins pipeline saved to {output_path}[/green]")


# ═══════════════════════════════════════════════════════════════════════════════
# GITLAB CI/CD GENERATOR
# ═══════════════════════════════════════════════════════════════════════════════


class GitLabCIGenerator:
    """Generate GitLab CI/CD configuration."""
    
    def generate(self, config: PipelineConfig) -> str:
        """Generate .gitlab-ci.yml content."""
        lines = [
            "# Generated by UnrealMate",
            f"# Project: {config.project_name}",
            "",
            "variables:",
            f"  UE_ROOT: '{config.ue_root}'",
            f"  PROJECT_NAME: '{config.project_name}'",
            "  BUILD_OUTPUT: './Build'",
            "",
            "stages:",
            "  - prepare",
            "  - build",
            "  - test",
            "  - deploy",
            "",
            "default:",
            "  tags:",
            "    - unreal",
            "    - windows",
            "",
            "# Prepare stage",
            "prepare:",
            "  stage: prepare",
            "  script:",
            "    - echo 'Preparing build environment...'",
            "  artifacts:",
            "    paths:",
            "      - Intermediate/",
            "    expire_in: 1 day",
            "",
        ]
        
        # Build jobs for each platform
        for platform in config.target_platforms:
            for build_config in config.configurations:
                job_name = f"build_{platform.value.lower()}_{build_config.value.lower()}"
                lines.extend([
                    f"# Build {platform.value} {build_config.value}",
                    f"{job_name}:",
                    "  stage: build",
                    "  script:",
                    f"    - $UE_ROOT/Engine/Build/BatchFiles/RunUAT.bat BuildCookRun",
                    f"      -project=\"$CI_PROJECT_DIR/$PROJECT_NAME.uproject\"",
                    f"      -targetplatform={platform.value}",
                    f"      -clientconfig={build_config.value}",
                ])
                
                if config.cook_content:
                    lines.append("      -cook")
                
                if config.package_build:
                    lines.append("      -stage -pak -package")
                
                if config.archive_build:
                    lines.append("      -archive -archivedirectory=\"$BUILD_OUTPUT\"")
                
                lines.extend([
                    "      -build -compile -unattended -utf8output",
                    "  artifacts:",
                    "    paths:",
                    "      - Build/",
                    "    expire_in: 1 week",
                    "  timeout: 4 hours",
                    "",
                ])
        
        # Test job
        if config.run_tests:
            lines.extend([
                "# Run automated tests",
                "test:",
                "  stage: test",
                "  script:",
                f"    - $UE_ROOT/Engine/Binaries/Win64/UnrealEditor-Cmd.exe",
                f"      \"$CI_PROJECT_DIR/$PROJECT_NAME.uproject\"",
                "      -ExecCmds=\"Automation RunTests Now\"",
                "      -Unattended -NoPause -Log",
                "  artifacts:",
                "    reports:",
                "      junit: TestResults/*.xml",
                "  allow_failure: true",
                "",
            ])
        
        # Deploy job
        lines.extend([
            "# Deploy (manual trigger)",
            "deploy:",
            "  stage: deploy",
            "  script:",
            "    - echo 'Deploying build...'",
            "  when: manual",
            "  only:",
            "    - main",
            "    - tags",
            "",
        ])
        
        return "\n".join(lines)
    
    def save(self, config: PipelineConfig, output_path: Path) -> None:
        """Save pipeline to file."""
        content = self.generate(config)
        output_path.write_text(content, encoding="utf-8")
        console.print(f"[green]✓ GitLab CI saved to {output_path}[/green]")


# ═══════════════════════════════════════════════════════════════════════════════
# AZURE DEVOPS PIPELINE GENERATOR
# ═══════════════════════════════════════════════════════════════════════════════


class AzureDevOpsPipelineGenerator:
    """Generate Azure DevOps pipeline configuration."""
    
    def generate(self, config: PipelineConfig) -> str:
        """Generate azure-pipelines.yml content."""
        lines = [
            "# Generated by UnrealMate",
            f"# Project: {config.project_name}",
            "",
            "trigger:",
            "  branches:",
            "    include:",
            "      - main",
            "      - develop",
            "",
            "pool:",
            "  vmImage: 'windows-latest'",
            "",
            "variables:",
            f"  UE_ROOT: '{config.ue_root}'",
            f"  PROJECT_NAME: '{config.project_name}'",
            "  BUILD_OUTPUT: '$(Build.ArtifactStagingDirectory)'",
            "",
            "stages:",
        ]
        
        # Build stage
        lines.extend([
            "- stage: Build",
            "  displayName: 'Build Stage'",
            "  jobs:",
        ])
        
        for platform in config.target_platforms:
            for build_config in config.configurations:
                job_name = f"Build_{platform.value}_{build_config.value}"
                lines.extend([
                    f"  - job: {job_name}",
                    f"    displayName: 'Build {platform.value} {build_config.value}'",
                    "    timeoutInMinutes: 240",
                    "    steps:",
                    "    - checkout: self",
                    "      clean: true",
                    "",
                    "    - task: CmdLine@2",
                    "      displayName: 'Build'",
                    "      inputs:",
                    "        script: |",
                    f"          \"$(UE_ROOT)/Engine/Build/BatchFiles/RunUAT.bat\" BuildCookRun ^",
                    f"          -project=\"$(Build.SourcesDirectory)/$(PROJECT_NAME).uproject\" ^",
                    f"          -targetplatform={platform.value} ^",
                    f"          -clientconfig={build_config.value} ^",
                ])
                
                if config.cook_content:
                    lines.append("          -cook ^")
                
                if config.package_build:
                    lines.append("          -stage -pak -package ^")
                
                if config.archive_build:
                    lines.append("          -archive -archivedirectory=\"$(BUILD_OUTPUT)\" ^")
                
                lines.extend([
                    "          -build -compile -unattended -utf8output",
                    "",
                    "    - task: PublishBuildArtifacts@1",
                    "      displayName: 'Publish Artifacts'",
                    "      inputs:",
                    f"        PathtoPublish: '$(BUILD_OUTPUT)'",
                    f"        ArtifactName: '{platform.value}_{build_config.value}'",
                    "",
                ])
        
        # Test stage
        if config.run_tests:
            lines.extend([
                "- stage: Test",
                "  displayName: 'Test Stage'",
                "  dependsOn: Build",
                "  jobs:",
                "  - job: RunTests",
                "    displayName: 'Run Automated Tests'",
                "    steps:",
                "    - task: CmdLine@2",
                "      displayName: 'Run Tests'",
                "      inputs:",
                "        script: |",
                f"          \"$(UE_ROOT)/Engine/Binaries/Win64/UnrealEditor-Cmd.exe\" ^",
                f"          \"$(Build.SourcesDirectory)/$(PROJECT_NAME).uproject\" ^",
                "          -ExecCmds=\"Automation RunTests Now\" ^",
                "          -Unattended -NoPause -Log",
                "",
            ])
        
        return "\n".join(lines)
    
    def save(self, config: PipelineConfig, output_path: Path) -> None:
        """Save pipeline to file."""
        content = self.generate(config)
        output_path.write_text(content, encoding="utf-8")
        console.print(f"[green]✓ Azure DevOps pipeline saved to {output_path}[/green]")


# ═══════════════════════════════════════════════════════════════════════════════
# DOCKER SUPPORT
# ═══════════════════════════════════════════════════════════════════════════════


class DockerfileGenerator:
    """Generate Dockerfile for UE builds."""
    
    def generate(self, config: PipelineConfig) -> str:
        """Generate Dockerfile content."""
        lines = [
            "# Generated by UnrealMate",
            f"# Project: {config.project_name}",
            "",
            "# Base image with Unreal Engine",
            "FROM ghcr.io/epicgames/unreal-engine:dev-5.4 AS builder",
            "",
            "# Set working directory",
            "WORKDIR /project",
            "",
            "# Copy project files",
            "COPY . .",
            "",
            "# Build the project",
            "RUN /UnrealEngine/Engine/Build/BatchFiles/RunUAT.sh BuildCookRun \\",
            f"    -project=/project/{config.project_name}.uproject \\",
            "    -targetplatform=Linux \\",
            "    -clientconfig=Shipping \\",
        ]
        
        if config.cook_content:
            lines.append("    -cook \\")
        
        if config.package_build:
            lines.append("    -stage -pak -package \\")
        
        lines.extend([
            "    -build -compile \\",
            "    -archive -archivedirectory=/project/Build \\",
            "    -unattended -utf8output",
            "",
            "# Runtime image",
            "FROM ubuntu:22.04 AS runtime",
            "",
            "# Install runtime dependencies",
            "RUN apt-get update && apt-get install -y \\",
            "    libgl1 libx11-6 libxcursor1 \\",
            "    && rm -rf /var/lib/apt/lists/*",
            "",
            "# Copy built game",
            f"COPY --from=builder /project/Build/Linux/{config.project_name} /game",
            "",
            "WORKDIR /game",
            "",
            "# Entry point",
            f'ENTRYPOINT ["./{config.project_name}"]',
            "",
        ])
        
        return "\n".join(lines)
    
    def generate_compose(self, config: PipelineConfig) -> str:
        """Generate docker-compose.yml content."""
        return f"""# Generated by UnrealMate
# Project: {config.project_name}

version: '3.8'

services:
  build:
    build:
      context: .
      dockerfile: Dockerfile
      target: builder
    volumes:
      - ./Build:/project/Build
    environment:
      - UE_ROOT=/UnrealEngine

  game-server:
    build:
      context: .
      dockerfile: Dockerfile
      target: runtime
    ports:
      - "7777:7777/udp"
    restart: unless-stopped
"""
    
    def save(self, config: PipelineConfig, output_dir: Path) -> None:
        """Save Docker files."""
        dockerfile = self.generate(config)
        compose = self.generate_compose(config)
        
        (output_dir / "Dockerfile").write_text(dockerfile, encoding="utf-8")
        (output_dir / "docker-compose.yml").write_text(compose, encoding="utf-8")
        
        console.print(f"[green]✓ Docker files saved to {output_dir}[/green]")


# ═══════════════════════════════════════════════════════════════════════════════
# BUILD VERSIONING
# ═══════════════════════════════════════════════════════════════════════════════


class BuildVersioning:
    """Automated build versioning."""
    
    def __init__(self, project_path: Path):
        self.project_path = project_path
        self.version_file = project_path / "Config" / "DefaultGame.ini"
    
    def get_version(self) -> str:
        """Get current project version."""
        if not self.version_file.exists():
            return "1.0.0"
        
        content = self.version_file.read_text(encoding="utf-8")
        
        import re
        match = re.search(r'ProjectVersion=(\d+\.\d+\.\d+)', content)
        if match:
            return match.group(1)
        
        return "1.0.0"
    
    def bump_version(
        self,
        bump_type: str = "patch",
    ) -> str:
        """Bump the project version."""
        current = self.get_version()
        parts = current.split(".")
        
        if len(parts) != 3:
            parts = ["1", "0", "0"]
        
        major, minor, patch = int(parts[0]), int(parts[1]), int(parts[2])
        
        if bump_type == "major":
            major += 1
            minor = 0
            patch = 0
        elif bump_type == "minor":
            minor += 1
            patch = 0
        else:  # patch
            patch += 1
        
        new_version = f"{major}.{minor}.{patch}"
        self._update_version(new_version)
        
        console.print(f"[green]✓ Version bumped: {current} → {new_version}[/green]")
        return new_version
    
    def _update_version(self, version: str) -> None:
        """Update version in config file."""
        if not self.version_file.exists():
            return
        
        content = self.version_file.read_text(encoding="utf-8")
        
        import re
        if "ProjectVersion=" in content:
            content = re.sub(
                r'ProjectVersion=\d+\.\d+\.\d+',
                f'ProjectVersion={version}',
                content,
            )
        else:
            # Add version if not exists
            content += f"\n[/Script/EngineSettings.GeneralProjectSettings]\nProjectVersion={version}\n"
        
        self.version_file.write_text(content, encoding="utf-8")
    
    def get_build_number(self) -> int:
        """Get current build number."""
        build_file = self.project_path / ".buildnumber"
        
        if build_file.exists():
            try:
                return int(build_file.read_text().strip())
            except ValueError:
                pass
        
        return 0
    
    def increment_build_number(self) -> int:
        """Increment and return new build number."""
        current = self.get_build_number()
        new_number = current + 1
        
        build_file = self.project_path / ".buildnumber"
        build_file.write_text(str(new_number), encoding="utf-8")
        
        return new_number


# ═══════════════════════════════════════════════════════════════════════════════
# CI/CD GENERATOR (MAIN CLASS)
# ═══════════════════════════════════════════════════════════════════════════════


class CICDGenerator:
    """Main class for generating CI/CD pipelines."""
    
    def __init__(self, project_path: Path):
        self.project_path = project_path
        self.jenkins = JenkinsPipelineGenerator()
        self.gitlab = GitLabCIGenerator()
        self.azure = AzureDevOpsPipelineGenerator()
        self.docker = DockerfileGenerator()
        self.versioning = BuildVersioning(project_path)
    
    def generate_all(
        self,
        config: PipelineConfig,
        output_dir: Optional[Path] = None,
    ) -> dict[str, Path]:
        """Generate all CI/CD configurations."""
        output = output_dir or self.project_path
        output.mkdir(parents=True, exist_ok=True)
        
        generated = {}
        
        # Jenkins
        jenkins_path = output / "Jenkinsfile"
        self.jenkins.save(config, jenkins_path)
        generated["jenkins"] = jenkins_path
        
        # GitLab
        gitlab_path = output / ".gitlab-ci.yml"
        self.gitlab.save(config, gitlab_path)
        generated["gitlab"] = gitlab_path
        
        # Azure DevOps
        azure_path = output / "azure-pipelines.yml"
        self.azure.save(config, azure_path)
        generated["azure"] = azure_path
        
        # Docker
        self.docker.save(config, output)
        generated["docker"] = output / "Dockerfile"
        
        return generated
    
    def generate_for_platform(
        self,
        platform: CIPlatform,
        config: PipelineConfig,
        output_path: Optional[Path] = None,
    ) -> Path:
        """Generate pipeline for a specific CI platform."""
        if platform == CIPlatform.JENKINS:
            path = output_path or self.project_path / "Jenkinsfile"
            self.jenkins.save(config, path)
            return path
        
        elif platform == CIPlatform.GITLAB:
            path = output_path or self.project_path / ".gitlab-ci.yml"
            self.gitlab.save(config, path)
            return path
        
        elif platform == CIPlatform.AZURE_DEVOPS:
            path = output_path or self.project_path / "azure-pipelines.yml"
            self.azure.save(config, path)
            return path
        
        else:
            raise ValueError(f"Unsupported CI platform: {platform}")
    
    def list_supported_platforms(self) -> Table:
        """List supported CI/CD platforms."""
        table = Table(title="Supported CI/CD Platforms")
        table.add_column("Platform", style="cyan")
        table.add_column("Output File")
        table.add_column("Status")
        
        platforms = [
            ("Jenkins", "Jenkinsfile", "✓ Supported"),
            ("GitLab CI/CD", ".gitlab-ci.yml", "✓ Supported"),
            ("Azure DevOps", "azure-pipelines.yml", "✓ Supported"),
            ("GitHub Actions", ".github/workflows/*.yml", "✓ Supported"),
            ("TeamCity", ".teamcity/settings.kts", "Coming Soon"),
            ("CircleCI", ".circleci/config.yml", "Coming Soon"),
        ]
        
        for name, output, status in platforms:
            style = "green" if "Supported" in status else "dim"
            table.add_row(name, output, f"[{style}]{status}[/{style}]")
        
        return table
