<?php

declare(strict_types=1);

namespace App\Framework\Deployment\Docker\Services;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Deployment\Docker\Events\ComposeDeploymentFailed;
use App\Framework\Deployment\Docker\Events\ComposeDeploymentStarted;
use App\Framework\Deployment\Docker\Events\ComposeDeploymentSucceeded;
use App\Framework\Deployment\Docker\ValueObjects\ComposeProject;
use App\Framework\Deployment\Docker\ValueObjects\ComposeResult;
use App\Framework\Deployment\Docker\ValueObjects\ComposeService;
use App\Framework\EventBus\EventBus;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Logging\Logger;
use App\Framework\Process\Process;
use App\Framework\Process\ValueObjects\Command;

/**
 * Docker Compose Service
 *
 * Orchestrates docker-compose operations for deployment:
 * - Multi-container application deployment
 * - Service scaling
 * - Configuration updates
 * - Health checks
 */
final readonly class DockerComposeService
{
    public function __construct(
        private Process $process,
        private EventBus $eventBus,
        private Logger $logger
    ) {
    }

    /**
     * Deploy application using docker-compose up
     */
    public function up(
        FilePath $composeFile,
        ?ComposeProject $project = null,
        bool $detached = true,
        bool $build = false
    ): ComposeResult {
        $startTime = microtime(true);

        $this->logger->info('Starting docker-compose up deployment', [
            'compose_file' => $composeFile->toString(),
            'project' => $project?->name,
            'detached' => $detached,
            'build' => $build
        ]);

        $this->eventBus->dispatch(new ComposeDeploymentStarted(
            composeFile: $composeFile,
            operation: 'up',
            timestamp: new \DateTimeImmutable()
        ));

        try {
            $args = ['docker-compose', '-f', $composeFile->toString()];

            if ($project !== null) {
                $args[] = '-p';
                $args[] = $project->name;
            }

            $args[] = 'up';

            if ($detached) {
                $args[] = '-d';
            }

            if ($build) {
                $args[] = '--build';
            }

            $result = $this->process->run(
                command: Command::fromArray($args),
                timeout: Duration::fromMinutes(10)
            );

            if (!$result->isSuccess()) {
                throw new \RuntimeException('docker-compose up failed: ' . $result->stderr);
            }

            $duration = Duration::fromSeconds(microtime(true) - $startTime);

            $composeResult = ComposeResult::success(
                operation: 'up',
                duration: $duration,
                output: $result->stdout
            );

            $this->eventBus->dispatch(new ComposeDeploymentSucceeded(
                composeFile: $composeFile,
                operation: 'up',
                duration: $duration,
                timestamp: new \DateTimeImmutable()
            ));

            $this->logger->info('docker-compose up succeeded', [
                'duration_ms' => $duration->toMilliseconds()
            ]);

            return $composeResult;

        } catch (\Throwable $e) {
            $duration = Duration::fromSeconds(microtime(true) - $startTime);

            $composeResult = ComposeResult::failure(
                operation: 'up',
                duration: $duration,
                error: $e->getMessage()
            );

            $this->eventBus->dispatch(new ComposeDeploymentFailed(
                composeFile: $composeFile,
                operation: 'up',
                error: $e->getMessage(),
                timestamp: new \DateTimeImmutable()
            ));

            $this->logger->error('docker-compose up failed', [
                'error' => $e->getMessage(),
                'duration_ms' => $duration->toMilliseconds()
            ]);

            return $composeResult;
        }
    }

    /**
     * Stop and remove containers using docker-compose down
     */
    public function down(
        FilePath $composeFile,
        ?ComposeProject $project = null,
        bool $removeVolumes = false
    ): ComposeResult {
        $startTime = microtime(true);

        $this->logger->info('Starting docker-compose down', [
            'compose_file' => $composeFile->toString(),
            'project' => $project?->name,
            'remove_volumes' => $removeVolumes
        ]);

        try {
            $args = ['docker-compose', '-f', $composeFile->toString()];

            if ($project !== null) {
                $args[] = '-p';
                $args[] = $project->name;
            }

            $args[] = 'down';

            if ($removeVolumes) {
                $args[] = '-v';
            }

            $result = $this->process->run(
                command: Command::fromArray($args),
                timeout: Duration::fromMinutes(5)
            );

            if (!$result->isSuccess()) {
                throw new \RuntimeException('docker-compose down failed: ' . $result->stderr);
            }

            $duration = Duration::fromSeconds(microtime(true) - $startTime);

            $this->logger->info('docker-compose down succeeded', [
                'duration_ms' => $duration->toMilliseconds()
            ]);

            return ComposeResult::success(
                operation: 'down',
                duration: $duration,
                output: $result->stdout
            );

        } catch (\Throwable $e) {
            $duration = Duration::fromSeconds(microtime(true) - $startTime);

            $this->logger->error('docker-compose down failed', [
                'error' => $e->getMessage()
            ]);

            return ComposeResult::failure(
                operation: 'down',
                duration: $duration,
                error: $e->getMessage()
            );
        }
    }

    /**
     * Restart services using docker-compose restart
     */
    public function restart(
        FilePath $composeFile,
        ?ComposeProject $project = null,
        ?array $services = null
    ): ComposeResult {
        $startTime = microtime(true);

        $this->logger->info('Starting docker-compose restart', [
            'compose_file' => $composeFile->toString(),
            'project' => $project?->name,
            'services' => $services
        ]);

        try {
            $args = ['docker-compose', '-f', $composeFile->toString()];

            if ($project !== null) {
                $args[] = '-p';
                $args[] = $project->name;
            }

            $args[] = 'restart';

            if ($services !== null) {
                $args = [...$args, ...$services];
            }

            $result = $this->process->run(
                command: Command::fromArray($args),
                timeout: Duration::fromMinutes(5)
            );

            if (!$result->isSuccess()) {
                throw new \RuntimeException('docker-compose restart failed: ' . $result->stderr);
            }

            $duration = Duration::fromSeconds(microtime(true) - $startTime);

            return ComposeResult::success(
                operation: 'restart',
                duration: $duration,
                output: $result->stdout
            );

        } catch (\Throwable $e) {
            $duration = Duration::fromSeconds(microtime(true) - $startTime);

            return ComposeResult::failure(
                operation: 'restart',
                duration: $duration,
                error: $e->getMessage()
            );
        }
    }

    /**
     * Get status of services using docker-compose ps
     */
    public function ps(FilePath $composeFile, ?ComposeProject $project = null): array
    {
        $args = ['docker-compose', '-f', $composeFile->toString()];

        if ($project !== null) {
            $args[] = '-p';
            $args[] = $project->name;
        }

        $args = [...$args, 'ps', '--format', 'json'];

        $result = $this->process->run(
            command: Command::fromArray($args),
            timeout: Duration::fromSeconds(10)
        );

        if (!$result->isSuccess()) {
            return [];
        }

        $services = [];
        $lines = explode("\n", trim($result->stdout));

        foreach ($lines as $line) {
            if (empty($line)) {
                continue;
            }

            $data = json_decode($line, true);
            if ($data === null) {
                continue;
            }

            $services[] = ComposeService::fromJson($data);
        }

        return $services;
    }

    /**
     * Pull latest images using docker-compose pull
     */
    public function pull(FilePath $composeFile, ?ComposeProject $project = null): ComposeResult
    {
        $startTime = microtime(true);

        $this->logger->info('Starting docker-compose pull', [
            'compose_file' => $composeFile->toString(),
            'project' => $project?->name
        ]);

        try {
            $args = ['docker-compose', '-f', $composeFile->toString()];

            if ($project !== null) {
                $args[] = '-p';
                $args[] = $project->name;
            }

            $args[] = 'pull';

            $result = $this->process->run(
                command: Command::fromArray($args),
                timeout: Duration::fromMinutes(15)
            );

            if (!$result->isSuccess()) {
                throw new \RuntimeException('docker-compose pull failed: ' . $result->stderr);
            }

            $duration = Duration::fromSeconds(microtime(true) - $startTime);

            return ComposeResult::success(
                operation: 'pull',
                duration: $duration,
                output: $result->stdout
            );

        } catch (\Throwable $e) {
            $duration = Duration::fromSeconds(microtime(true) - $startTime);

            return ComposeResult::failure(
                operation: 'pull',
                duration: $duration,
                error: $e->getMessage()
            );
        }
    }
}
