<?php

declare(strict_types=1);

namespace ImovelZapAi\Conversations\Application\Queries;

use Carbon\CarbonInterface;
use Illuminate\Support\Collection;
use ImovelZapAi\Conversations\Application\Data\PersonDetailData;
use ImovelZapAi\Conversations\Application\Data\PersonTimelineItemData;
use ImovelZapAi\Conversations\Application\Services\PersonTimelineClassifier;
use ImovelZapAi\Conversations\Domain\Enums\ConversationStatus;
use ImovelZapAi\Conversations\Domain\Enums\HandoffStatus;
use ImovelZapAi\Conversations\Domain\Enums\MessageDirection;
use ImovelZapAi\Conversations\Domain\Enums\TimelineActor;
use ImovelZapAi\Conversations\Infrastructure\Persistence\Eloquent\ContactModel;
use ImovelZapAi\Conversations\Infrastructure\Persistence\Eloquent\ConversationModel;
use ImovelZapAi\Conversations\Infrastructure\Persistence\Eloquent\HumanHandoffModel;
use ImovelZapAi\Conversations\Infrastructure\Persistence\Eloquent\MessageModel;
use ImovelZapAi\Properties\Domain\Enums\PropertyInterestStatus;
use ImovelZapAi\Properties\Infrastructure\Persistence\Eloquent\PropertyInterestModel;
use ImovelZapAi\Tenancy\Support\TenantContext;

final class PersonTimelineQuery
{
    public function __construct(
        private readonly PersonTimelineClassifier $classifier,
    ) {}

    public function forContact(
        string $tenantId,
        string $contactId,
        ?string $conversationId = null,
    ): PersonDetailData {
        TenantContext::set($tenantId);

        $contact = ContactModel::query()->whereKey($contactId)->firstOrFail();

        $conversationsQuery = ConversationModel::query()
            ->where('tenant_id', $tenantId)
            ->where('contact_id', $contactId)
            ->orderByDesc('last_message_at');

        if ($conversationId !== null) {
            $conversationsQuery->whereKey($conversationId);
        }

        /** @var Collection<int, ConversationModel> $conversations */
        $conversations = $conversationsQuery->get();

        $conversation = $conversationId !== null
            ? $conversations->first()
            : $conversations->first();

        $conversationIds = $conversations->pluck('id')->all();

        $claimedAtByConversation = HumanHandoffModel::query()
            ->where('tenant_id', $tenantId)
            ->where('contact_id', $contactId)
            ->whereNotNull('claimed_at')
            ->pluck('claimed_at', 'conversation_id');

        $timeline = collect();
        $messages = collect();

        if ($conversationIds !== []) {
            $messages = MessageModel::query()
                ->where('tenant_id', $tenantId)
                ->whereIn('conversation_id', $conversationIds)
                ->orderBy('created_at')
                ->get();

            foreach ($messages as $message) {
                $item = $this->timelineItemFromMessage(
                    $message,
                    $claimedAtByConversation->get((string) $message->conversation_id),
                );

                if ($item !== null) {
                    $timeline->push($item);
                }
            }
        }

        $interests = PropertyInterestModel::query()
            ->where('tenant_id', $tenantId)
            ->where('contact_id', $contactId)
            ->when($conversationId !== null, fn ($query) => $query->where('conversation_id', $conversationId))
            ->with(['property', 'assignedUser'])
            ->orderBy('created_at')
            ->get();

        foreach ($interests as $interest) {
            $propertyLabel = $interest->property?->internal_code
                ?? $interest->property?->title
                ?? 'imóvel';

            $timeline->push(new PersonTimelineItemData(
                occurredAt: $interest->created_at ?? now(),
                label: 'Solicitou visita em '.$propertyLabel,
                actor: TimelineActor::Client,
                detail: $interest->property?->title,
            ));

            if (in_array($interest->status, [PropertyInterestStatus::ClosedWon, PropertyInterestStatus::ClosedRental, PropertyInterestStatus::Lost], true)) {
                $timeline->push(new PersonTimelineItemData(
                    occurredAt: $interest->updated_at ?? now(),
                    label: match ($interest->status) {
                        PropertyInterestStatus::ClosedWon => 'Negócio fechado',
                        PropertyInterestStatus::ClosedRental => 'Aluguel concluído',
                        PropertyInterestStatus::Lost => 'Cliente desistiu',
                        default => 'Finalizado',
                    },
                    actor: TimelineActor::Broker,
                    actorName: $interest->assignedUser?->name,
                ));
            }
        }

        $handoffs = HumanHandoffModel::query()
            ->where('tenant_id', $tenantId)
            ->where('contact_id', $contactId)
            ->when($conversationId !== null, fn ($query) => $query->where('conversation_id', $conversationId))
            ->with(['assignedUser', 'summary'])
            ->orderBy('queued_at')
            ->get();

        foreach ($handoffs as $handoff) {
            $timeline->push(new PersonTimelineItemData(
                occurredAt: $handoff->queued_at ?? $handoff->created_at ?? now(),
                label: 'Transferido ao corretor',
                actor: TimelineActor::System,
                detail: $handoff->summary?->body ? 'Resumo gerado' : null,
            ));

            if ($handoff->claimed_at !== null) {
                $timeline->push(new PersonTimelineItemData(
                    occurredAt: $handoff->claimed_at,
                    label: 'Corretor assumiu o atendimento',
                    actor: TimelineActor::Broker,
                    actorName: $handoff->assignedUser?->name,
                ));
            }
        }

        $timeline = $timeline
            ->sortBy(fn (PersonTimelineItemData $item): int => $item->occurredAt->getTimestamp())
            ->values();

        $timeline = $this->deduplicateTimeline($timeline);

        [$lastMessagePreview, $lastInteractionAt, $lastResponderLabel] = $this->resolveLastInteraction(
            $messages ?? collect(),
            $timeline,
            $interests,
            $handoffs,
        );

        return new PersonDetailData(
            contact: $contact,
            conversation: $conversation,
            statusLabel: $this->resolveStatusLabel($conversation, $interests, $handoffs),
            resultLabel: $this->resolveResultLabel($interests),
            lastMessagePreview: $lastMessagePreview,
            lastInteractionAt: $lastInteractionAt,
            lastResponderLabel: $lastResponderLabel,
            timeline: $timeline,
        );
    }

    private function timelineItemFromMessage(
        MessageModel $message,
        mixed $claimedAt,
    ): ?PersonTimelineItemData {
        $body = trim((string) ($message->body ?? ''));

        if ($body === '' || $this->classifier->shouldSkipMessage($message)) {
            return null;
        }

        $occurredAt = $message->sent_at ?? $message->created_at ?? now();
        $isBrokerPhase = $this->isBrokerMessage($message, $claimedAt);

        $label = $this->classifier->labelForMessage($message, $isBrokerPhase);

        if ($label === null) {
            return null;
        }

        $actor = match (true) {
            $message->direction === MessageDirection::Inbound => TimelineActor::Client,
            $isBrokerPhase => TimelineActor::Broker,
            default => TimelineActor::Assistant,
        };

        $detail = mb_strlen($body) > 160 ? mb_substr($body, 0, 157).'…' : $body;

        return new PersonTimelineItemData(
            occurredAt: $occurredAt,
            label: $label,
            actor: $actor,
            detail: $detail,
        );
    }

    /**
     * @param  Collection<int, PersonTimelineItemData>  $timeline
     * @return Collection<int, PersonTimelineItemData>
     */
    private function deduplicateTimeline(Collection $timeline): Collection
    {
        $seen = [];

        return $timeline->filter(function (PersonTimelineItemData $item) use (&$seen): bool {
            $key = $item->label.'|'.$item->occurredAt->format('Y-m-d H:i');

            if (isset($seen[$key])) {
                return false;
            }

            $seen[$key] = true;

            return true;
        })->values();
    }

    /**
     * @param  Collection<int, MessageModel>  $messages
     * @param  Collection<int, PersonTimelineItemData>  $timeline
     * @param  Collection<int, PropertyInterestModel>  $interests
     * @param  Collection<int, HumanHandoffModel>  $handoffs
     * @return array{0: ?string, 1: ?CarbonInterface, 2: ?string}
     */
    private function resolveLastInteraction(
        Collection $messages,
        Collection $timeline,
        Collection $interests,
        Collection $handoffs,
    ): array {
        $lastMessage = $messages->last();
        $lastMessagePreview = $lastMessage !== null
            ? mb_substr(trim((string) ($lastMessage->body ?? '')), 0, 120)
            : null;

        $lastInteractionAt = collect([
            $lastMessage?->created_at,
            $interests->max('updated_at'),
            $handoffs->max('claimed_at'),
            $handoffs->max('queued_at'),
        ])->filter()->max();

        $lastOutbound = $messages
            ->where('direction', MessageDirection::Outbound)
            ->last();

        $lastResponderLabel = null;

        if ($lastOutbound !== null) {
            $payload = is_array($lastOutbound->raw_payload) ? $lastOutbound->raw_payload : [];

            if (($payload['sent_by'] ?? null) === 'broker') {
                $lastResponderLabel = is_string($payload['user_name'] ?? null) && $payload['user_name'] !== ''
                    ? $payload['user_name']
                    : 'Corretor';
            } else {
                $claimedAt = $handoffs->whereNotNull('claimed_at')->max('claimed_at');
                $outboundAt = $lastOutbound->sent_at ?? $lastOutbound->created_at;

                if ($claimedAt !== null && $outboundAt !== null && $outboundAt->greaterThanOrEqualTo($claimedAt)) {
                    $broker = $handoffs->sortByDesc('claimed_at')->first()?->assignedUser;
                    $lastResponderLabel = $broker?->name ?? 'Corretor';
                } else {
                    $lastResponderLabel = 'Assistente';
                }
            }
        }

        return [$lastMessagePreview ?: null, $lastInteractionAt, $lastResponderLabel];
    }

    private function isBrokerMessage(MessageModel $message, mixed $claimedAt): bool
    {
        $payload = is_array($message->raw_payload) ? $message->raw_payload : [];

        if (($payload['sent_by'] ?? null) === 'broker') {
            return true;
        }

        if (! $claimedAt instanceof CarbonInterface) {
            return false;
        }

        $occurredAt = $message->sent_at ?? $message->created_at ?? now();

        return $occurredAt->greaterThanOrEqualTo($claimedAt);
    }

    /**
     * @param  Collection<int, PropertyInterestModel>  $interests
     * @param  Collection<int, HumanHandoffModel>  $handoffs
     */
    private function resolveStatusLabel(
        ?ConversationModel $conversation,
        Collection $interests,
        Collection $handoffs,
    ): string {
        if ($handoffs->contains(fn (HumanHandoffModel $h): bool => $h->status === HandoffStatus::Waiting)) {
            return 'Pronta para atendimento';
        }

        $activeInterest = $interests->first(fn (PropertyInterestModel $i): bool => in_array($i->status, [
            PropertyInterestStatus::InProgress,
            PropertyInterestStatus::VisitScheduled,
            PropertyInterestStatus::Negotiating,
        ], true));

        if ($activeInterest !== null) {
            return 'Em negociação';
        }

        if ($interests->contains(fn (PropertyInterestModel $i): bool => in_array($i->status, [
            PropertyInterestStatus::New,
            PropertyInterestStatus::Qualifying,
            PropertyInterestStatus::ReadyForHandoff,
        ], true))) {
            return 'Interessada';
        }

        if ($conversation?->status !== null) {
            return $conversation->status->personLabel();
        }

        return 'Sem conversa ativa';
    }

    /**
     * @param  Collection<int, PropertyInterestModel>  $interests
     */
    private function resolveResultLabel(Collection $interests): ?string
    {
        $won = $interests->contains(fn (PropertyInterestModel $i): bool => $i->status === PropertyInterestStatus::ClosedWon);
        $rented = $interests->contains(fn (PropertyInterestModel $i): bool => $i->status === PropertyInterestStatus::ClosedRental);
        $lost = $interests->contains(fn (PropertyInterestModel $i): bool => $i->status === PropertyInterestStatus::Lost);

        if ($won) {
            return 'Fechada com sucesso';
        }

        if ($rented) {
            return 'Aluguel concluído';
        }

        if ($lost) {
            return 'Desistiu';
        }

        if ($interests->isNotEmpty() && $interests->every(fn (PropertyInterestModel $i): bool => in_array($i->status, [
            PropertyInterestStatus::ClosedWon,
            PropertyInterestStatus::ClosedRental,
            PropertyInterestStatus::Lost,
        ], true))) {
            return 'Finalizada';
        }

        return null;
    }
}
