<?php

declare(strict_types=1);

namespace ImovelZapAi\Sales\Application\Actions;

use App\Models\User;
use Illuminate\Support\Facades\DB;
use ImovelZapAi\Conversations\Application\Actions\UpdatePersonInterestStageAction;
use ImovelZapAi\Properties\Domain\Enums\PropertyInterestStatus;
use ImovelZapAi\Properties\Infrastructure\Persistence\Eloquent\PropertyInterestModel;
use ImovelZapAi\Sales\Application\Services\DealActivityRecorder;
use ImovelZapAi\Sales\Application\Services\DealHistoryWriter;
use ImovelZapAi\Sales\Application\Services\DefaultPipelineSeeder;
use ImovelZapAi\Sales\Domain\Enums\DealActivityType;
use ImovelZapAi\Sales\Domain\Enums\DealHistoryEvent;
use ImovelZapAi\Sales\Domain\Enums\DealLostReason;
use ImovelZapAi\Sales\Domain\Enums\DealStageKey;
use ImovelZapAi\Sales\Domain\Events\DealLost;
use ImovelZapAi\Sales\Domain\Events\DealStageChanged;
use ImovelZapAi\Sales\Domain\Events\DealWon;
use ImovelZapAi\Sales\Domain\Exceptions\DealException;
use ImovelZapAi\Sales\Infrastructure\Persistence\Eloquent\DealModel;
use ImovelZapAi\Sales\Infrastructure\Persistence\Eloquent\SalesDealStageModel;

final class ChangeDealStageAction
{
    public function __construct(
        private readonly DefaultPipelineSeeder $pipelineSeeder,
        private readonly DealActivityRecorder $activities,
        private readonly DealHistoryWriter $history,
        private readonly UpdatePersonInterestStageAction $updateInterestStage,
        private readonly CreateDealTaskAction $createTask,
    ) {}

    public function execute(
        DealModel $deal,
        User $actor,
        string $stageKey,
        ?string $lostReason = null,
        ?string $lostNotes = null,
    ): DealModel {
        if ($deal->isClosed() && ! in_array($stageKey, [DealStageKey::Won->value, DealStageKey::Lost->value], true)) {
            throw DealException::alreadyClosed();
        }

        $from = $deal->stage ?? SalesDealStageModel::query()->find($deal->stage_id);
        $to = $this->pipelineSeeder->stageByKey($deal->tenant_id, $stageKey, $deal->pipeline_id);

        if ($from !== null && $from->id === $to->id) {
            return $deal;
        }

        return DB::transaction(function () use ($deal, $actor, $from, $to, $lostReason, $lostNotes): DealModel {
            $before = [
                'stage_id' => $deal->stage_id,
                'stage_key' => $from?->key,
            ];

            $deal->forceFill([
                'stage_id' => $to->id,
                'stage_entered_at' => now(),
                'won_at' => $to->is_won ? now() : null,
                'lost_at' => $to->is_lost ? now() : null,
                'lost_reason' => $to->is_lost
                    ? (DealLostReason::tryFrom((string) $lostReason)?->value ?? $lostReason)
                    : null,
                'lost_notes' => $to->is_lost ? $lostNotes : null,
                'closed_at' => $to->isTerminal() ? now() : null,
            ])->save();

            $this->activities->record(
                $deal,
                DealActivityType::StageChanged,
                'Estágio: '.($from?->name ?? '?').' → '.$to->name,
                actorUserId: (int) $actor->id,
                meta: ['from' => $from?->key, 'to' => $to->key],
            );

            if ($to->key === DealStageKey::Proposal->value) {
                $this->activities->record(
                    $deal,
                    DealActivityType::ProposalSent,
                    'Proposta enviada',
                    actorUserId: (int) $actor->id,
                );
            }

            if ($to->key === DealStageKey::VisitDone->value) {
                $this->activities->record(
                    $deal,
                    DealActivityType::VisitCompleted,
                    'Visita realizada',
                    actorUserId: (int) $actor->id,
                );

                if (config('sales.automation.visit_done_creates_task', true)) {
                    $this->createTask->execute($deal->fresh(), $actor, [
                        'type' => 'follow_up',
                        'title' => 'Atualizar oportunidade após visita',
                        'due_at' => now()->addDay()->toDateTimeString(),
                        'is_automatic' => true,
                        'priority' => 'high',
                    ]);
                }
            }

            $this->history->write(
                $deal->tenant_id,
                $deal->id,
                DealHistoryEvent::StageChanged,
                actorUserId: (int) $actor->id,
                before: $before,
                after: ['stage_id' => $to->id, 'stage_key' => $to->key],
                message: 'Estágio alterado.',
            );

            DealStageChanged::dispatch(
                $deal->tenant_id,
                $deal->id,
                (string) ($from?->key ?? ''),
                (string) $to->key,
            );

            if ($to->is_won) {
                $this->activities->record($deal, DealActivityType::Won, 'Negócio concluído', actorUserId: (int) $actor->id);
                $this->history->write($deal->tenant_id, $deal->id, DealHistoryEvent::Won, actorUserId: (int) $actor->id, message: 'Concluído.');
                $this->syncInterestWonLost($deal, PropertyInterestStatus::ClosedWon);
                DealWon::dispatch($deal->tenant_id, $deal->id);

                if (config('sales.automation.won_starts_post_sale', true)) {
                    $this->createTask->execute($deal->fresh(), $actor, [
                        'type' => 'post_sale',
                        'title' => 'Iniciar pós-venda',
                        'due_at' => now()->addDays(2)->toDateTimeString(),
                        'is_automatic' => true,
                    ]);
                }
            }

            if ($to->is_lost) {
                $this->activities->record($deal, DealActivityType::Lost, 'Negócio perdido', $lostNotes, (int) $actor->id);
                $this->history->write($deal->tenant_id, $deal->id, DealHistoryEvent::Lost, actorUserId: (int) $actor->id, message: $lostReason);
                $this->syncInterestWonLost($deal, PropertyInterestStatus::Lost);
                DealLost::dispatch($deal->tenant_id, $deal->id, $lostReason);
            }

            return $deal->fresh(['stage', 'tasks', 'contact', 'property']);
        });
    }

    private function syncInterestWonLost(DealModel $deal, PropertyInterestStatus $status): void
    {
        if ($deal->property_interest_id === null) {
            return;
        }

        $interest = PropertyInterestModel::query()->find($deal->property_interest_id);
        if ($interest === null) {
            return;
        }

        if ($interest->status === $status || ($interest->status?->canTransitionTo($status) ?? false)) {
            $this->updateInterestStage->execute($deal->tenant_id, $interest, $status);
        }
    }
}
