<?php

namespace App\Domain\Crew\Actions;

use App\Domain\Agent\Models\Agent;
use App\Domain\Crew\Enums\CrewTaskStatus;
use App\Domain\Crew\Models\CrewExecution;
use App\Domain\Crew\Models\CrewMember;
use App\Domain\Crew\Models\CrewTaskExecution;
use App\Infrastructure\AI\Contracts\AiGatewayInterface;
use App\Infrastructure\AI\DTOs\AiRequestDTO;
use App\Infrastructure\AI\Services\ProviderResolver;

class ValidateTaskOutputAction
{
    public function __construct(
        private readonly AiGatewayInterface $gateway,
        private readonly ProviderResolver $providerResolver,
    ) {}

    /**
     * Have the QA agent validate a task's output.
     *
     * @return array{passed: bool, score: float, feedback: string, issues: array, criterion_scores?: array<string, float>}
     */
    public function execute(CrewTaskExecution $taskExecution, CrewExecution $execution): array
    {
        $config = $execution->config_snapshot;
        $qaAgent = Agent::withoutGlobalScopes()->find($config['qa_agent']['id']);

        if (! $qaAgent) {
            throw new \RuntimeException('QA agent not found.');
        }

        $qaMember = CrewMember::forAgentInCrew($qaAgent->id, $execution->crew_id);
        $resolved = $qaMember
            ? $this->providerResolver->forCrewRole($qaMember)
            : $this->providerResolver->resolve(agent: $qaAgent);

        $rubric = $this->resolveRubric($taskExecution, $config);
        $qualityThreshold = $rubric['min_score'] ?? $config['quality_threshold'] ?? 0.70;

        $systemPrompt = "You are {$qaAgent->role}. {$qaAgent->goal}\n\n"
            ."Evaluate the output of a completed task.\n"
            .$this->buildRubricSection($rubric)
            ."The quality threshold is {$qualityThreshold} (0.0-1.0).\n\n"
            .'Respond with valid JSON: { "passed": bool, "score": float (0.0-1.0), "feedback": string, "issues": [string], "criterion_scores": {"criterion_name": float} }';

        $expectedOutput = $taskExecution->input_context['expected_output'] ?? 'No specific format expected';

        $userPrompt = "Task: {$taskExecution->title}\n"
            ."Description: {$taskExecution->description}\n"
            ."Expected output: {$expectedOutput}\n\n"
            ."Actual output:\n".json_encode($taskExecution->output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)."\n\n"
            .'Evaluate this output.';

        $request = new AiRequestDTO(
            provider: $resolved['provider'],
            model: $resolved['model'],
            systemPrompt: $systemPrompt,
            userPrompt: $userPrompt,
            maxTokens: 2048,
            userId: $execution->resolveUserId(),
            teamId: $execution->team_id,
            agentId: $qaAgent->id,
            purpose: 'crew.validate_task',
            temperature: 0.2,
        );

        $response = $this->gateway->complete($request);

        $validation = $this->parseValidation($response->content);

        // Update task with QA results
        $passed = $validation['passed'] && $validation['score'] >= $qualityThreshold;

        $taskExecution->update([
            'qa_feedback' => $validation,
            'qa_score' => $validation['score'],
            'status' => $passed ? CrewTaskStatus::Validated : CrewTaskStatus::NeedsRevision,
        ]);

        $qaScore = $validation['score'];
        $stance = round(($qaScore - 0.5) * 2, 4);
        $confidence = round($qaScore, 4);
        $attemptRatio = max(0, round(1 - (($taskExecution->attempt_number - 1) / max($taskExecution->max_attempts, 1)), 4));

        $taskExecution->update([
            'belief_state' => [
                'stance' => $stance,
                'confidence' => $confidence,
                'attempt_ratio' => $attemptRatio,
                'updated_at' => now()->toIso8601String(),
            ],
        ]);

        // Track cost on execution
        $execution->increment('total_cost_credits', $response->usage->costCredits);

        return $validation;
    }

    /**
     * Parallel commit-reveal validation: run QA agent N times independently and aggregate.
     * Each pass is isolated — the agent cannot see prior assessments (commit phase).
     * Results are aggregated by majority vote on pass/fail and mean score (reveal phase).
     *
     * @return array{passed: bool, score: float, feedback: string, issues: array, criterion_scores?: array<string, float>, parallel_rounds?: int, agreement_ratio?: float}
     */
    public function executeParallel(CrewTaskExecution $taskExecution, CrewExecution $execution, int $rounds = 3): array
    {
        $config = $execution->config_snapshot;
        $qaAgent = Agent::withoutGlobalScopes()->find($config['qa_agent']['id']);

        if (! $qaAgent) {
            throw new \RuntimeException('QA agent not found.');
        }

        $qaMember = CrewMember::forAgentInCrew($qaAgent->id, $execution->crew_id);
        $resolved = $qaMember
            ? $this->providerResolver->forCrewRole($qaMember)
            : $this->providerResolver->resolve(agent: $qaAgent);

        $rubric = $this->resolveRubric($taskExecution, $config);
        $qualityThreshold = $rubric['min_score'] ?? $config['quality_threshold'] ?? 0.70;

        $systemPrompt = "You are {$qaAgent->role}. {$qaAgent->goal}\n\n"
            ."Evaluate the output of a completed task independently.\n"
            .$this->buildRubricSection($rubric)
            ."The quality threshold is {$qualityThreshold} (0.0-1.0).\n\n"
            .'Respond with valid JSON: { "passed": bool, "score": float (0.0-1.0), "feedback": string, "issues": [string], "criterion_scores": {"criterion_name": float} }';

        $expectedOutput = $taskExecution->input_context['expected_output'] ?? 'No specific format expected';
        $userPrompt = "Task: {$taskExecution->title}\n"
            ."Description: {$taskExecution->description}\n"
            ."Expected output: {$expectedOutput}\n\n"
            ."Actual output:\n".json_encode($taskExecution->output, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)."\n\n"
            .'Evaluate this output.';

        // Commit phase: collect independent assessments
        $assessments = [];
        $totalCost = 0;

        for ($i = 0; $i < $rounds; $i++) {
            $request = new AiRequestDTO(
                provider: $resolved['provider'],
                model: $resolved['model'],
                systemPrompt: $systemPrompt,
                userPrompt: $userPrompt,
                maxTokens: 2048,
                userId: $execution->resolveUserId(),
                teamId: $execution->team_id,
                agentId: $qaAgent->id,
                purpose: 'crew.validate_task.parallel',
                temperature: 0.5,
            );

            try {
                $response = $this->gateway->complete($request);
                $assessments[] = $this->parseValidation($response->content);
                $totalCost += $response->usage->costCredits;
            } catch (\Throwable) {
                // Skip failed rounds — don't let one failure abort the whole process
            }
        }

        if (empty($assessments)) {
            return $this->execute($taskExecution, $execution);
        }

        // Reveal phase: aggregate by majority vote on pass/fail, mean on score
        $passCount = count(array_filter($assessments, fn ($a) => $a['passed']));
        $majorityPassed = $passCount > count($assessments) / 2;
        $meanScore = array_sum(array_column($assessments, 'score')) / count($assessments);
        $agreementRatio = max($passCount, count($assessments) - $passCount) / count($assessments);

        $merged = [
            'passed' => $majorityPassed && $meanScore >= $qualityThreshold,
            'score' => round($meanScore, 4),
            'feedback' => $assessments[0]['feedback'],
            'issues' => array_values(array_unique(array_merge(...array_column($assessments, 'issues')))),
            'criterion_scores' => $assessments[0]['criterion_scores'] ?? [],
            'parallel_rounds' => count($assessments),
            'agreement_ratio' => round($agreementRatio, 4),
        ];

        $taskExecution->update([
            'qa_feedback' => $merged,
            'qa_score' => $merged['score'],
            'status' => $merged['passed'] ? CrewTaskStatus::Validated : CrewTaskStatus::NeedsRevision,
        ]);

        $qaScore = $merged['score'];
        $stance = round(($qaScore - 0.5) * 2, 4);
        $confidence = round($qaScore, 4);
        $attemptRatio = max(0, round(1 - (($taskExecution->attempt_number - 1) / max($taskExecution->max_attempts, 1)), 4));

        $taskExecution->update([
            'belief_state' => [
                'stance' => $stance,
                'confidence' => $confidence,
                'attempt_ratio' => $attemptRatio,
                'updated_at' => now()->toIso8601String(),
            ],
        ]);

        $execution->increment('total_cost_credits', $totalCost);

        return $merged;
    }

    /**
     * Resolve the rubric for the task from crew config, matching by keyword in task title/description.
     * Falls back to the "default" rubric, then to an empty array (generic evaluation).
     *
     * @param  array<string, mixed>  $config
     * @return array<string, mixed>
     */
    private function resolveRubric(CrewTaskExecution $taskExecution, array $config): array
    {
        $rubrics = $config['task_rubrics'] ?? [];

        if (empty($rubrics)) {
            return [];
        }

        $taskText = strtolower($taskExecution->title.' '.$taskExecution->description);

        // Try each rubric key (except "default") as a keyword match against the task text
        foreach ($rubrics as $key => $rubric) {
            if ($key === 'default') {
                continue;
            }

            if (str_contains($taskText, strtolower($key))) {
                return $rubric;
            }
        }

        return $rubrics['default'] ?? [];
    }

    /**
     * Build the rubric instruction section for the QA system prompt.
     *
     * @param  array<string, mixed>  $rubric
     */
    private function buildRubricSection(array $rubric): string
    {
        $criteria = $rubric['criteria'] ?? [];

        if (empty($criteria)) {
            return "Check for: accuracy, completeness, relevance to the task description, and quality.\n";
        }

        $lines = ['Evaluate against these weighted criteria:'];
        foreach ($criteria as $criterion) {
            $weightPct = (int) round(($criterion['weight'] ?? 0) * 100);
            $lines[] = "- **{$criterion['name']}** ({$weightPct}%): {$criterion['description']}";
        }
        $lines[] = '';

        return implode("\n", $lines)."\n";
    }

    /**
     * @return array{passed: bool, score: float, feedback: string, issues: array, criterion_scores: array<string, float>}
     */
    private function parseValidation(string $content): array
    {
        $content = trim($content);
        if (str_starts_with($content, '```')) {
            $content = preg_replace('/^```(?:json)?\s*\n?/', '', $content);
            $content = preg_replace('/\n?```\s*$/', '', $content);
        }

        $parsed = json_decode($content, true, 512, JSON_UNESCAPED_UNICODE);

        if (! is_array($parsed) || ! isset($parsed['passed'], $parsed['score'], $parsed['feedback'])) {
            // Fallback: QA response was unparseable, treat as failed
            return [
                'passed' => false,
                'score' => 0.0,
                'feedback' => 'QA agent did not produce a valid validation response.',
                'issues' => ['Invalid QA response format'],
                'criterion_scores' => [],
            ];
        }

        return [
            'passed' => (bool) $parsed['passed'],
            'score' => (float) min(1.0, max(0.0, $parsed['score'])),
            'feedback' => (string) $parsed['feedback'],
            'issues' => $parsed['issues'] ?? [],
            'criterion_scores' => is_array($parsed['criterion_scores'] ?? null) ? $parsed['criterion_scores'] : [],
        ];
    }
}
