<?php

namespace App\Services\FlowTester;

use Exception;

/**
 * 의존성 정렬기 (Topological Sort)
 *
 * 플로우 스텝의 dependsOn 속성을 분석하여
 * 올바른 실행 순서를 결정합니다.
 *
 * 지원하는 의존성 형식:
 * 1. 단순 문자열: "step_id" - 해당 스텝이 성공해야 실행
 * 2. 조건부 객체: {"step": "step_id", "onlyIf": "success|failure|executed|skipped"}
 *    - success: 해당 스텝이 성공했을 때만 실행 (기본값)
 *    - failure: 해당 스텝이 실패했을 때만 실행
 *    - executed: 해당 스텝이 실행되었을 때만 실행 (성공/실패 무관, 스킵 제외)
 *    - skipped: 해당 스텝이 스킵되었을 때만 실행
 *    - any: 해당 스텝의 결과와 무관하게 순서만 보장
 */
class DependencyResolver
{
    /**
     * 의존성 기반 실행 순서 결정
     *
     * @param  array  $steps  스텝 정의 배열
     * @return array 정렬된 스텝 ID 배열
     *
     * @throws Exception 순환 의존성 발견 시
     */
    public function resolve(array $steps): array
    {
        $graph = [];
        $inDegree = [];
        $stepMap = [];

        // 그래프 초기화
        foreach ($steps as $step) {
            $id = $step['id'];
            $graph[$id] = [];
            $inDegree[$id] = 0;
            $stepMap[$id] = $step;
        }

        // 간선 추가 (의존성)
        foreach ($steps as $step) {
            $id = $step['id'];
            $deps = $step['dependsOn'] ?? [];

            foreach ($deps as $dep) {
                // 조건부 의존성 또는 단순 문자열 지원
                $depId = $this->extractDependencyId($dep);

                if (! isset($graph[$depId])) {
                    throw new Exception("Unknown dependency: '{$depId}' in step '{$id}'");
                }
                $graph[$depId][] = $id;
                $inDegree[$id]++;
            }
        }

        // Kahn's Algorithm
        $queue = [];
        foreach ($inDegree as $id => $degree) {
            if ($degree === 0) {
                $queue[] = $id;
            }
        }

        $sorted = [];
        while (! empty($queue)) {
            $current = array_shift($queue);
            $sorted[] = $current;

            foreach ($graph[$current] as $neighbor) {
                $inDegree[$neighbor]--;
                if ($inDegree[$neighbor] === 0) {
                    $queue[] = $neighbor;
                }
            }
        }

        if (count($sorted) !== count($steps)) {
            // 순환 의존성 발견 - 어떤 스텝들이 문제인지 파악
            $remaining = array_diff(array_keys($graph), $sorted);
            throw new Exception('Circular dependency detected in steps: '.implode(', ', $remaining));
        }

        return $sorted;
    }

    /**
     * 의존성에서 스텝 ID 추출
     *
     * @param  mixed  $dependency  의존성 (문자열 또는 조건부 객체)
     * @return string 스텝 ID
     */
    private function extractDependencyId(mixed $dependency): string
    {
        if (is_string($dependency)) {
            return $dependency;
        }

        if (is_array($dependency) && isset($dependency['step'])) {
            return $dependency['step'];
        }

        throw new Exception('Invalid dependency format: '.json_encode($dependency));
    }

    /**
     * 의존성 조건 파싱
     *
     * @param  mixed  $dependency  의존성 (문자열 또는 조건부 객체)
     * @return array ['stepId' => string, 'condition' => string]
     */
    public function parseDependency(mixed $dependency): array
    {
        if (is_string($dependency)) {
            return [
                'stepId' => $dependency,
                'condition' => 'success', // 기본값: 성공 시에만
            ];
        }

        if (is_array($dependency)) {
            return [
                'stepId' => $dependency['step'] ?? '',
                'condition' => $dependency['onlyIf'] ?? 'success',
            ];
        }

        return [
            'stepId' => '',
            'condition' => 'success',
        ];
    }

    /**
     * 의존성 그래프 시각화 (디버깅용)
     *
     * @param  array  $steps  스텝 정의 배열
     * @return array 의존성 정보
     */
    public function visualize(array $steps): array
    {
        $result = [];

        foreach ($steps as $step) {
            $id = $step['id'];
            $deps = $step['dependsOn'] ?? [];

            $result[$id] = [
                'name' => $step['name'] ?? $id,
                'depends_on' => $deps,
                'depended_by' => [],
            ];
        }

        // 역방향 의존성 추가
        foreach ($steps as $step) {
            $id = $step['id'];
            $deps = $step['dependsOn'] ?? [];

            foreach ($deps as $dep) {
                if (isset($result[$dep])) {
                    $result[$dep]['depended_by'][] = $id;
                }
            }
        }

        return $result;
    }

    /**
     * 의존성 유효성 검사
     *
     * @param  array  $steps  스텝 정의 배열
     * @return array ['valid' => bool, 'errors' => array]
     */
    public function validate(array $steps): array
    {
        $errors = [];
        $stepIds = array_column($steps, 'id');

        // 중복 ID 체크
        $duplicates = array_diff_assoc($stepIds, array_unique($stepIds));
        if (! empty($duplicates)) {
            $errors[] = 'Duplicate step IDs found: '.implode(', ', array_unique($duplicates));
        }

        // 존재하지 않는 의존성 체크
        foreach ($steps as $step) {
            $id = $step['id'];
            $deps = $step['dependsOn'] ?? [];

            foreach ($deps as $dep) {
                // 조건부 의존성 지원
                $parsed = $this->parseDependency($dep);
                $depId = $parsed['stepId'];

                if (! in_array($depId, $stepIds)) {
                    $errors[] = "Step '{$id}' depends on unknown step '{$depId}'";
                }

                // 자기 참조 체크
                if ($depId === $id) {
                    $errors[] = "Step '{$id}' cannot depend on itself";
                }

                // 조건부 의존성 조건값 검증
                $validConditions = ['success', 'failure', 'executed', 'skipped', 'any'];
                if (! in_array($parsed['condition'], $validConditions)) {
                    $errors[] = "Step '{$id}': Invalid dependency condition '{$parsed['condition']}'. Valid values: ".implode(', ', $validConditions);
                }
            }
        }

        // 순환 의존성 체크
        try {
            $this->resolve($steps);
        } catch (Exception $e) {
            $errors[] = $e->getMessage();
        }

        return [
            'valid' => empty($errors),
            'errors' => $errors,
        ];
    }
}
