<?php

namespace App\Http\Controllers\Api;

use App\Events\DealStageChanged;
use App\Http\Controllers\Controller;
use App\Http\Resources\DealResource;
use App\Models\Deal;
use App\Models\DealStage;
use App\Support\CrmModuleManager;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

class DealController extends Controller
{
    public function __construct()
    {
        $this->authorizeResource(Deal::class, 'deal');
    }

    public function index(Request $request)
    {
        $pipelineId = $request->integer('pipeline_id');
        $status = $request->input('status');
        $search = trim((string) $request->input('q', ''));

        $deals = Deal::query()
            ->with(['pipeline', 'stage', 'company', 'contact', 'owner'])
            ->when($pipelineId, fn ($query) => $query->where('pipeline_id', $pipelineId))
            ->when($status, fn ($query) => $query->where('status', $status))
            ->when($search !== '', function ($query) use ($search): void {
                $query->where(function ($sub) use ($search): void {
                    $sub->where('title', 'like', "%{$search}%")
                        ->orWhereHas('company', fn ($company) => $company->where('name', 'like', "%{$search}%"))
                        ->orWhereHas('contact', fn ($contact) => $contact->where('first_name', 'like', "%{$search}%")->orWhere('last_name', 'like', "%{$search}%"));
                });
            })
            ->latest()
            ->paginate(20)
            ->withQueryString();

        return DealResource::collection($deals);
    }

    public function store(Request $request, CrmModuleManager $moduleManager): DealResource
    {
        $payload = $this->validatedData($request);
        $payload = $moduleManager->applyPayloadHooks('deals.store', $payload, [
            'hook' => 'deals.store',
            'user_id' => $request->user()->id,
            'source' => 'api',
        ], array_keys($payload));

        $deal = Deal::create($payload);

        return DealResource::make($deal->load(['pipeline', 'stage', 'company', 'contact', 'owner']));
    }

    public function show(Deal $deal): DealResource
    {
        return DealResource::make($deal->load(['pipeline', 'stage', 'company', 'contact', 'owner']));
    }

    public function update(Request $request, Deal $deal, CrmModuleManager $moduleManager): DealResource
    {
        $payload = $this->validatedData($request, $deal);
        $payload = $moduleManager->applyPayloadHooks('deals.update', $payload, [
            'hook' => 'deals.update',
            'user_id' => $request->user()->id,
            'deal_id' => $deal->id,
            'source' => 'api',
        ], array_keys($payload));

        $deal->update($payload);

        return DealResource::make($deal->load(['pipeline', 'stage', 'company', 'contact', 'owner']));
    }

    public function destroy(Deal $deal)
    {
        $deal->delete();

        return response()->noContent();
    }

    public function updateStage(Request $request, Deal $deal): DealResource
    {
        $this->authorize('update', $deal);

        $validated = $request->validate([
            'stage_id' => ['required', 'exists:deal_stages,id'],
            'status' => ['nullable', Rule::in(['open', 'won', 'lost'])],
        ]);

        $stage = DealStage::query()
            ->whereKey((int) $validated['stage_id'])
            ->where('pipeline_id', $deal->pipeline_id)
            ->firstOrFail();

        $status = $validated['status'] ?? $deal->status;

        if ($stage->is_won) {
            $status = 'won';
        } elseif ($stage->is_lost) {
            $status = 'lost';
        }

        $deal->update([
            'stage_id' => $stage->id,
            'status' => $status,
            'closed_at' => $status === 'open' ? null : ($deal->closed_at ?? now()),
        ]);

        $deal->refresh()->load(['pipeline', 'stage', 'company', 'contact', 'owner']);

        $this->broadcastToOthers(new DealStageChanged($deal));

        return DealResource::make($deal);
    }

    /**
     * @return array<string, mixed>
     */
    private function validatedData(Request $request, ?Deal $deal = null): array
    {
        $validated = $request->validate([
            'title' => ['required', 'string', 'max:255'],
            'pipeline_id' => ['required', 'exists:pipelines,id'],
            'stage_id' => ['required', 'exists:deal_stages,id'],
            'company_id' => ['nullable', 'exists:companies,id'],
            'contact_id' => ['nullable', 'exists:contacts,id'],
            'owner_id' => ['nullable', 'exists:users,id'],
            'amount' => ['required', 'numeric', 'min:0'],
            'currency' => ['required', 'string', 'size:3'],
            'priority' => ['required', Rule::in(['low', 'medium', 'high'])],
            'status' => ['required', Rule::in(['open', 'won', 'lost'])],
            'expected_close_at' => ['nullable', 'date'],
            'source' => ['nullable', 'string', 'max:255'],
            'lost_reason' => ['nullable', 'string'],
            'description' => ['nullable', 'string'],
        ]);

        $stage = DealStage::query()->findOrFail((int) $validated['stage_id']);

        if ($stage->pipeline_id !== (int) $validated['pipeline_id']) {
            throw ValidationException::withMessages([
                'stage_id' => 'The selected stage does not belong to the selected funnel.',
            ]);
        }

        $status = $validated['status'];
        if ($stage->is_won) {
            $status = 'won';
        } elseif ($stage->is_lost) {
            $status = 'lost';
        }

        if ($status === 'lost' && trim((string) ($validated['lost_reason'] ?? '')) === '') {
            throw ValidationException::withMessages([
                'lost_reason' => 'Indicate the reason for losing the transaction.',
            ]);
        }

        $validated['currency'] = strtoupper($validated['currency']);
        $validated['status'] = $status;
        $validated['closed_at'] = $status === 'open'
            ? null
            : ($deal && $deal->status === $status && $deal->closed_at ? $deal->closed_at : now());

        return $validated;
    }
}
