<?php

declare(strict_types=1);

namespace ImovelZapAi\Sales\Presentation\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use ImovelZapAi\Sales\Application\Actions\AddDealNoteAction;
use ImovelZapAi\Sales\Application\Actions\ChangeDealStageAction;
use ImovelZapAi\Sales\Application\Actions\CompleteDealTaskAction;
use ImovelZapAi\Sales\Application\Actions\CreateDealAction;
use ImovelZapAi\Sales\Application\Actions\CreateDealFromConversationAction;
use ImovelZapAi\Sales\Application\Actions\CreateDealTaskAction;
use ImovelZapAi\Sales\Application\Actions\RunDealAutomationsAction;
use ImovelZapAi\Sales\Application\Actions\UpdateDealAction;
use ImovelZapAi\Sales\Application\Queries\GetDealDashboardQuery;
use ImovelZapAi\Sales\Application\Queries\GetDealKanbanQuery;
use ImovelZapAi\Sales\Application\Queries\GetDealTimelineQuery;
use ImovelZapAi\Sales\Application\Queries\ListDealsQuery;
use ImovelZapAi\Sales\Application\Services\DefaultPipelineSeeder;
use ImovelZapAi\Sales\Domain\Enums\DealLostReason;
use ImovelZapAi\Sales\Domain\Enums\DealPriority;
use ImovelZapAi\Sales\Domain\Enums\DealTaskType;
use ImovelZapAi\Sales\Domain\Exceptions\DealException;
use ImovelZapAi\Sales\Infrastructure\Persistence\Eloquent\DealModel;
use ImovelZapAi\Sales\Infrastructure\Persistence\Eloquent\DealTaskModel;
use ImovelZapAi\Tenancy\Support\TenantContext;

final class DealController extends Controller
{
    public function index(Request $request, ListDealsQuery $query): JsonResponse
    {
        return response()->json($query->execute((string) TenantContext::id(), [
            'stage_key' => $request->query('stage_key'),
            'responsible_user_id' => $request->query('responsible_user_id'),
            'contact_id' => $request->query('contact_id'),
            'open_only' => filter_var($request->query('open_only', false), FILTER_VALIDATE_BOOL),
            'per_page' => (int) $request->query('per_page', 20),
        ]));
    }

    public function store(Request $request, CreateDealAction $action): JsonResponse
    {
        $data = $request->validate([
            'contact_id' => ['required', 'uuid', 'exists:contacts,id'],
            'title' => ['nullable', 'string', 'max:255'],
            'property_id' => ['nullable', 'uuid', 'exists:properties,id'],
            'conversation_id' => ['nullable', 'uuid', 'exists:conversations,id'],
            'property_interest_id' => ['nullable', 'uuid', 'exists:property_interests,id'],
            'amount' => ['nullable', 'numeric'],
            'priority' => ['nullable', 'in:'.implode(',', DealPriority::values())],
            'stage_key' => ['nullable', 'string'],
            'next_action_at' => ['nullable', 'date'],
            'next_action_type' => ['nullable', 'in:'.implode(',', DealTaskType::values())],
            'next_action_notes' => ['nullable', 'string', 'max:500'],
            'responsible_user_id' => ['nullable', 'integer', 'exists:users,id'],
        ]);

        $deal = $action->execute((string) TenantContext::id(), $request->user(), $data);

        return response()->json($this->serialize($deal), 201);
    }

    public function fromConversation(Request $request, string $conversationId, CreateDealFromConversationAction $action): JsonResponse
    {
        $extra = $request->validate([
            'amount' => ['nullable', 'numeric'],
            'priority' => ['nullable', 'in:'.implode(',', DealPriority::values())],
            'property_id' => ['nullable', 'uuid'],
            'property_interest_id' => ['nullable', 'uuid'],
            'stage_key' => ['nullable', 'string'],
            'next_action_at' => ['nullable', 'date'],
            'next_action_type' => ['nullable', 'string'],
        ]);

        try {
            $deal = $action->execute((string) TenantContext::id(), $request->user(), $conversationId, $extra);
        } catch (DealException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }

        return response()->json($this->serialize($deal), 201);
    }

    public function show(string $id, GetDealTimelineQuery $timeline): JsonResponse
    {
        $deal = $this->find($id)->load(['stage', 'contact', 'property', 'responsible', 'tasks', 'notes', 'tags']);

        return response()->json([
            'deal' => $this->serialize($deal),
            'timeline' => $timeline->execute($deal),
        ]);
    }

    public function update(string $id, Request $request, UpdateDealAction $action): JsonResponse
    {
        $data = $request->validate([
            'title' => ['nullable', 'string', 'max:255'],
            'amount' => ['nullable', 'numeric'],
            'priority' => ['nullable', 'in:'.implode(',', DealPriority::values())],
            'responsible_user_id' => ['nullable', 'integer', 'exists:users,id'],
            'property_id' => ['nullable', 'uuid', 'exists:properties,id'],
            'next_action_at' => ['nullable', 'date'],
            'next_action_type' => ['nullable', 'in:'.implode(',', DealTaskType::values())],
            'next_action_notes' => ['nullable', 'string', 'max:500'],
        ]);

        try {
            $deal = $action->execute($this->find($id), $request->user(), $data);
        } catch (DealException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }

        return response()->json($this->serialize($deal));
    }

    public function changeStage(string $id, Request $request, ChangeDealStageAction $action): JsonResponse
    {
        $data = $request->validate([
            'stage_key' => ['required', 'string'],
            'lost_reason' => ['nullable', 'in:'.implode(',', DealLostReason::values())],
            'lost_notes' => ['nullable', 'string'],
        ]);

        try {
            $deal = $action->execute(
                $this->find($id),
                $request->user(),
                $data['stage_key'],
                $data['lost_reason'] ?? null,
                $data['lost_notes'] ?? null,
            );
        } catch (DealException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }

        return response()->json($this->serialize($deal));
    }

    public function storeNote(string $id, Request $request, AddDealNoteAction $action): JsonResponse
    {
        $data = $request->validate(['body' => ['required', 'string']]);

        try {
            $note = $action->execute($this->find($id), $request->user(), $data['body']);
        } catch (DealException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }

        return response()->json(['id' => $note->id, 'body' => $note->body], 201);
    }

    public function storeTask(string $id, Request $request, CreateDealTaskAction $action): JsonResponse
    {
        $data = $request->validate([
            'type' => ['required', 'in:'.implode(',', DealTaskType::values())],
            'title' => ['required', 'string', 'max:255'],
            'description' => ['nullable', 'string'],
            'due_at' => ['nullable', 'date'],
            'priority' => ['nullable', 'in:'.implode(',', DealPriority::values())],
            'responsible_user_id' => ['nullable', 'integer', 'exists:users,id'],
            'set_as_next_action' => ['nullable', 'boolean'],
        ]);

        try {
            $task = $action->execute($this->find($id), $request->user(), $data);
        } catch (DealException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }

        return response()->json(['id' => $task->id, 'title' => $task->title, 'status' => $task->status->value], 201);
    }

    public function completeTask(string $dealId, string $taskId, Request $request, CompleteDealTaskAction $action): JsonResponse
    {
        $task = DealTaskModel::query()
            ->where('tenant_id', TenantContext::id())
            ->where('deal_id', $dealId)
            ->whereKey($taskId)
            ->firstOrFail();

        try {
            $task = $action->execute($task, $request->user());
        } catch (DealException $e) {
            return response()->json(['message' => $e->getMessage()], 422);
        }

        return response()->json(['id' => $task->id, 'status' => $task->status->value]);
    }

    public function kanban(GetDealKanbanQuery $query): JsonResponse
    {
        return response()->json($query->execute((string) TenantContext::id()));
    }

    public function dashboard(GetDealDashboardQuery $query): JsonResponse
    {
        return response()->json($query->execute((string) TenantContext::id()));
    }

    public function pipeline(DefaultPipelineSeeder $seeder): JsonResponse
    {
        $pipeline = $seeder->ensureForTenant((string) TenantContext::id());

        return response()->json([
            'id' => $pipeline->id,
            'name' => $pipeline->name,
            'stages' => $pipeline->stages->map(fn ($s) => [
                'id' => $s->id,
                'key' => $s->key,
                'name' => $s->name,
                'position' => $s->position,
                'is_won' => $s->is_won,
                'is_lost' => $s->is_lost,
                'color' => $s->color,
            ])->values(),
        ]);
    }

    public function runAutomations(Request $request, RunDealAutomationsAction $action): JsonResponse
    {
        $result = $action->execute((string) TenantContext::id(), $request->user());

        return response()->json($result);
    }

    private function find(string $id): DealModel
    {
        return DealModel::query()
            ->where('tenant_id', TenantContext::id())
            ->whereKey($id)
            ->firstOrFail();
    }

    /**
     * @return array<string, mixed>
     */
    private function serialize(DealModel $deal): array
    {
        return [
            'id' => $deal->id,
            'title' => $deal->title,
            'amount' => $deal->amount,
            'currency' => $deal->currency,
            'priority' => $deal->priority->value ?? $deal->priority,
            'stage_id' => $deal->stage_id,
            'stage_key' => $deal->stage?->key,
            'stage_name' => $deal->stage?->name,
            'contact_id' => $deal->contact_id,
            'property_id' => $deal->property_id,
            'conversation_id' => $deal->conversation_id,
            'property_interest_id' => $deal->property_interest_id,
            'responsible_user_id' => $deal->responsible_user_id,
            'next_action_at' => $deal->next_action_at?->toIso8601String(),
            'next_action_type' => $deal->next_action_type->value ?? $deal->next_action_type,
            'next_action_notes' => $deal->next_action_notes,
            'stage_entered_at' => $deal->stage_entered_at?->toIso8601String(),
            'last_activity_at' => $deal->last_activity_at?->toIso8601String(),
            'won_at' => $deal->won_at?->toIso8601String(),
            'lost_at' => $deal->lost_at?->toIso8601String(),
            'lost_reason' => $deal->lost_reason,
            'closed_at' => $deal->closed_at?->toIso8601String(),
            'contact' => $deal->relationLoaded('contact') && $deal->contact ? [
                'id' => $deal->contact->id,
                'display_name' => $deal->contact->display_name,
            ] : null,
            'property' => $deal->relationLoaded('property') && $deal->property ? [
                'id' => $deal->property->id,
                'title' => $deal->property->title,
            ] : null,
            'created_at' => $deal->created_at?->toIso8601String(),
        ];
    }
}
