<?php

namespace App\Http\Controllers\Api\V1;

use App\Domain\Crew\Actions\CreateCrewAction;
use App\Domain\Crew\Actions\ExecuteCrewAction;
use App\Domain\Crew\Actions\UpdateCrewAction;
use App\Domain\Crew\Enums\CrewProcessType;
use App\Domain\Crew\Enums\CrewStatus;
use App\Domain\Crew\Models\Crew;
use App\Domain\Crew\Models\CrewExecution;
use App\Domain\Orchestration\Exceptions\CostGateExceededException;
use App\Domain\Orchestration\Services\OrchestrationCostEstimator;
use App\Domain\Orchestration\Services\OrchestrationCostGate;
use App\Http\Controllers\Api\V1\Concerns\DocumentsResponses;
use App\Http\Controllers\Controller;
use App\Http\Resources\Api\V1\CrewExecutionResource;
use App\Http\Resources\Api\V1\CrewResource;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
use Spatie\QueryBuilder\AllowedFilter;
use Spatie\QueryBuilder\QueryBuilder;

/**
 * @tags Crews
 */
class CrewController extends Controller
{
    use DocumentsResponses;

    public function index(Request $request): AnonymousResourceCollection
    {
        $crews = QueryBuilder::for(Crew::class)
            ->allowedFilters(
                AllowedFilter::exact('status'),
                AllowedFilter::exact('process_type'),
                AllowedFilter::partial('name'),
            )
            ->allowedSorts('created_at', 'updated_at', 'name', 'status')
            ->defaultSort('-created_at')
            ->with(['coordinator', 'qaAgent'])
            ->withCount('executions')
            ->cursorPaginate(min((int) $request->input('per_page', 15), 100));

        return CrewResource::collection($crews);
    }

    public function show(Crew $crew): CrewResource
    {
        return new CrewResource(
            $crew->load(['coordinator', 'qaAgent', 'members.agent'])
                ->loadCount('executions'),
        );
    }

    public function store(Request $request, CreateCrewAction $action): JsonResponse
    {
        $teamId = $request->user()->current_team_id;

        $request->validate([
            'name' => ['required', 'string', 'max:255'],
            'description' => ['sometimes', 'nullable', 'string'],
            'coordinator_agent_id' => ['required', 'uuid', Rule::exists('agents', 'id')->where('team_id', $teamId)],
            'qa_agent_id' => ['nullable', 'uuid', Rule::exists('agents', 'id')->where('team_id', $teamId)],
            'process_type' => ['sometimes', new Enum(CrewProcessType::class)],
            'max_task_iterations' => ['sometimes', 'integer', 'min:1', 'max:20'],
            'quality_threshold' => ['sometimes', 'numeric', 'min:0', 'max:1'],
            'worker_agent_ids' => ['sometimes', 'array'],
            'worker_agent_ids.*' => ['uuid', Rule::exists('agents', 'id')->where('team_id', $teamId)],
            'convergence_mode' => ['sometimes', 'string', Rule::in(['any_validated', 'all_validated', 'threshold_ratio', 'quality_gate'])],
            'min_validated_count' => ['sometimes', 'nullable', 'integer', 'min:1'],
            'min_validated_ratio' => ['sometimes', 'nullable', 'numeric', 'min:0', 'max:1'],
            'settings' => ['sometimes', 'array'],
        ]);

        $settings = $request->input('settings', []);
        if ($request->has('convergence_mode')) {
            $settings['convergence_mode'] = $request->convergence_mode;
        }
        if ($request->has('min_validated_count')) {
            $settings['min_validated_count'] = $request->min_validated_count;
        }
        if ($request->has('min_validated_ratio')) {
            $settings['min_validated_ratio'] = $request->min_validated_ratio;
        }

        $crew = $action->execute(
            userId: $request->user()->id,
            name: $request->name,
            coordinatorAgentId: $request->coordinator_agent_id,
            qaAgentId: $request->input('qa_agent_id'),
            description: $request->description,
            processType: $request->process_type
                ? CrewProcessType::from($request->process_type)
                : CrewProcessType::Hierarchical,
            maxTaskIterations: $request->input('max_task_iterations', 3),
            qualityThreshold: (float) $request->input('quality_threshold', 0.70),
            workerAgentIds: $request->input('worker_agent_ids', []),
            settings: $settings,
            teamId: $request->user()->current_team_id,
        );

        return (new CrewResource($crew->load(['coordinator', 'qaAgent', 'members.agent'])))
            ->response()
            ->setStatusCode(201);
    }

    public function update(Request $request, Crew $crew, UpdateCrewAction $action): CrewResource
    {
        $teamId = $request->user()->current_team_id;

        $request->validate([
            'name' => ['sometimes', 'string', 'max:255'],
            'description' => ['sometimes', 'nullable', 'string'],
            'coordinator_agent_id' => ['sometimes', 'uuid', Rule::exists('agents', 'id')->where('team_id', $teamId)],
            'qa_agent_id' => ['sometimes', 'uuid', Rule::exists('agents', 'id')->where('team_id', $teamId)],
            'process_type' => ['sometimes', new Enum(CrewProcessType::class)],
            'max_task_iterations' => ['sometimes', 'integer', 'min:1', 'max:20'],
            'quality_threshold' => ['sometimes', 'numeric', 'min:0', 'max:1'],
            'worker_agent_ids' => ['sometimes', 'array'],
            'worker_agent_ids.*' => ['uuid', Rule::exists('agents', 'id')->where('team_id', $teamId)],
            'status' => ['sometimes', new Enum(CrewStatus::class)],
            'convergence_mode' => ['sometimes', 'string', Rule::in(['any_validated', 'all_validated', 'threshold_ratio', 'quality_gate'])],
            'min_validated_count' => ['sometimes', 'nullable', 'integer', 'min:1'],
            'min_validated_ratio' => ['sometimes', 'nullable', 'numeric', 'min:0', 'max:1'],
            'settings' => ['sometimes', 'array'],
        ]);

        $settings = $request->input('settings');
        if ($request->has('convergence_mode') || $request->has('min_validated_count') || $request->has('min_validated_ratio')) {
            $settings = $settings ?? [];
            if ($request->has('convergence_mode')) {
                $settings['convergence_mode'] = $request->convergence_mode;
            }
            if ($request->has('min_validated_count')) {
                $settings['min_validated_count'] = $request->min_validated_count;
            }
            if ($request->has('min_validated_ratio')) {
                $settings['min_validated_ratio'] = $request->min_validated_ratio;
            }
        }

        $crew = $action->execute(
            crew: $crew,
            name: $request->input('name'),
            description: $request->input('description'),
            coordinatorAgentId: $request->input('coordinator_agent_id'),
            qaAgentId: $request->input('qa_agent_id'),
            processType: $request->process_type
                ? CrewProcessType::from($request->process_type)
                : null,
            maxTaskIterations: $request->input('max_task_iterations'),
            qualityThreshold: $request->has('quality_threshold')
                ? (float) $request->quality_threshold
                : null,
            workerAgentIds: $request->input('worker_agent_ids'),
            status: $request->status ? CrewStatus::from($request->status) : null,
            settings: $settings,
        );

        return new CrewResource($crew->load(['coordinator', 'qaAgent', 'members.agent']));
    }

    /**
     * @response 200 {"message": "Crew deleted."}
     */
    public function destroy(Crew $crew): JsonResponse
    {
        $crew->delete();

        return response()->json(['message' => 'Crew deleted.']);
    }

    public function execute(Request $request, Crew $crew, ExecuteCrewAction $action): JsonResponse
    {
        $request->validate([
            'goal' => ['required', 'string', 'max:2000'],
            'experiment_id' => ['sometimes', 'nullable', 'uuid', Rule::exists('experiments', 'id')->where('team_id', $request->user()?->current_team_id)],
            'confirm_cost' => ['sometimes', 'boolean'],
        ]);

        try {
            $execution = $action->execute(
                crew: $crew,
                goal: $request->goal,
                teamId: $request->user()->current_team_id,
                experimentId: $request->input('experiment_id'),
                costConfirmed: $request->boolean('confirm_cost'),
            );
        } catch (CostGateExceededException $e) {
            return response()->json([
                'error' => 'cost_confirmation_required',
                'message' => $e->getMessage(),
                'projected_credits' => $e->projectedCredits,
                'threshold_credits' => $e->thresholdCredits,
                'hint' => 'Re-submit with confirm_cost=true to proceed.',
            ], 402);
        }

        return (new CrewExecutionResource($execution))
            ->invalidates('crews')
            ->response()
            ->setStatusCode(201);
    }

    public function costEstimate(Request $request, Crew $crew, OrchestrationCostEstimator $estimator, OrchestrationCostGate $gate): JsonResponse
    {
        $projected = $estimator->estimateCrew($crew);
        $team = $request->user()?->currentTeam;

        return response()->json([
            'data' => [
                'projected_credits' => $projected,
                'threshold_credits' => $gate->thresholdFor($team),
                'gate_enabled' => $gate->enabled(),
                'requires_confirmation' => $gate->requiresConfirmation($projected, $team),
            ],
        ]);
    }

    public function executions(Request $request, Crew $crew): AnonymousResourceCollection
    {
        $executions = QueryBuilder::for(
            CrewExecution::query()->where('crew_id', $crew->id),
        )
            ->allowedFilters(
                AllowedFilter::exact('status'),
            )
            ->allowedSorts('created_at', 'status', 'total_cost_credits', 'duration_ms')
            ->defaultSort('-created_at')
            ->cursorPaginate(min((int) $request->input('per_page', 15), 100));

        return CrewExecutionResource::collection($executions);
    }

    public function showExecution(Crew $crew, CrewExecution $execution): CrewExecutionResource
    {
        if ($execution->crew_id !== $crew->id) {
            abort(404);
        }

        return new CrewExecutionResource($execution->load('taskExecutions'));
    }
}
