<?php

namespace App\Http\Controllers;

use App\Agent\Choices;
use App\Agent\TikTokAdsAgent;
use App\Agent\Workspace;
use App\Campaigns\TikTokCampaignFlow;
use App\Models\TikTokConnection;
use App\Support\Markdown;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Laravel\Ai\Models\Conversation;
use Throwable;

class TikTokChatController extends Controller
{
    public function __construct(
        private readonly Workspace $workspace,
        private readonly TikTokCampaignFlow $flow,
    ) {}

    public function show(Request $request): View
    {
        $conversation = $this->resolveConversation($request);

        if ($conversation) {
            $this->workspace->bindTo($conversation->id);
        }

        $transcript = $this->transcript($conversation);
        $connection = TikTokConnection::query()->where('user_id', $request->user()->getKey())->first();
        $advertiserVerified = filled($connection?->advertiser_name)
            && collect($connection?->advertisers ?? [])->contains(fn ($advertiser): bool => is_array($advertiser)
                && (string) ($advertiser['advertiser_id'] ?? '') === (string) $connection->advertiser_id
            );
        $configuredAdvertiserSelected = filled(config('services.tiktok.advertiser_id'))
            && (string) $connection?->advertiser_id === (string) config('services.tiktok.advertiser_id');
        $advertiserSelected = $advertiserVerified || $configuredAdvertiserSelected;
        $draft = $this->draft($conversation, $connection);
        $tiktokConnected = $connection !== null && filled($connection->access_token);
        $choices = $conversation ? $this->workspace->pendingChoices() : [];
        if ($choices === [] && $transcript !== []) {
            $lastMessage = $transcript[array_key_last($transcript)];
            if ($lastMessage['role'] === 'agent') {
                $this->offerFallbackChoices($lastMessage['text']);
                $choices = app(Choices::class)->all();
                if ($choices !== [] && $conversation) {
                    $this->workspace->rememberChoices($choices);
                }
                app(Choices::class)->drain();
            }
        }

        return view('tiktokchat', [
            'pageTitle' => 'TikTok Ads chat',
            'conversationId' => $conversation?->id,
            'transcript' => $transcript,
            'awaiting' => [],
            'choices' => $choices,
            'conversations' => $this->recentConversations($request),
            'connection' => $connection,
            'tiktokConnected' => $tiktokConnected,
            'campaignStarted' => filled($draft['objective'] ?? null),
            'advertiserSelected' => $advertiserSelected,
            'advertiserVerified' => $advertiserVerified,
            'configuredAdvertiserId' => config('services.tiktok.advertiser_id'),
            'panelHtml' => $this->panelHtml($draft),
        ]);
    }

    public function create(): RedirectResponse
    {
        return to_route('tiktokchat.show', ['new' => 1]);
    }

    public function send(Request $request): JsonResponse
    {
        $connection = TikTokConnection::query()->where('user_id', $request->user()->getKey())->first();
        if ($connection === null || blank($connection->access_token)) {
            return response()->json(['message' => 'Connect TikTok Ads before continuing.'], 409);
        }

        $validated = $request->validate([
            'message' => ['required', 'string', 'max:4000'],
            'conversation' => ['nullable', 'string', 'max:36'],
            'answers' => ['nullable', 'array'],
        ]);

        $message = trim($validated['message']);
        foreach ($validated['answers'] ?? [] as $answer) {
            if (is_array($answer) && filled($answer['field'] ?? null) && filled($answer['value'] ?? null)) {
                $value = is_array($answer['value']) ? implode(', ', $answer['value']) : $answer['value'];
                $message .= ' Set '.$answer['field'].' to '.$value.'.';
            }
        }

        $agent = $validated['conversation'] ?? null
            ? $this->agent($request)->continue(
                $this->ownedConversation($request, $validated['conversation'])->id,
                $request->user(),
            )
            : $this->agent($request)->forParticipant($request->user());

        if ($validated['conversation'] ?? null) {
            $this->workspace->bindTo($validated['conversation']);
            $this->workspace->rememberChoices([]);
        }

        try {
            $response = $agent->prompt($message);
            $reply = trim((string) $response->text);
            $this->offerFallbackChoices($reply);
            $choices = app(Choices::class)->all();
            app(Choices::class)->drain();
            $this->workspace->bindTo($agent->currentConversation());
            $this->workspace->rememberChoices($choices);
            $conversation = $this->rememberConversation($agent->currentConversation());
            $connection = TikTokConnection::query()->where('user_id', $request->user()->getKey())->first();
            $this->rememberAnswers($conversation, $validated['answers'] ?? [], $connection);
        } catch (Throwable $exception) {
            report($exception);
            $reply = 'That TikTok Ads step failed: '.$exception->getMessage();
            $this->recordFailedTurn($agent->currentConversation(), $request, $message, $reply);
        }

        $reply = $reply !== '' ? $reply : 'TikTok Ads did not return a text response.';
        $conversation ??= $this->rememberConversation($agent->currentConversation());
        $connection = TikTokConnection::query()->where('user_id', $request->user()->getKey())->first();

        return response()->json([
            'conversation' => $agent->currentConversation(),
            'conversation_title' => $this->conversationTitle($agent->currentConversation()),
            'reply' => $reply,
            'replyHtml' => (string) Markdown::toHtml($reply),
            'awaiting_approval' => [],
            'choices' => $choices ?? [],
            'panelHtml' => $this->panelHtml($this->draft($conversation, $connection)),
        ]);
    }

    private function agent(Request $request): TikTokAdsAgent
    {
        return new TikTokAdsAgent($request, $this->workspace);
    }

    /** @return array<string, mixed> */
    private function draft(?Conversation $conversation, ?TikTokConnection $connection): array
    {
        $draft = json_decode((string) $conversation?->tiktok_campaign_draft, true);
        $draft = is_array($draft) ? $draft : [];

        return array_filter([
            ...$draft,
            'advertiser_id' => $connection?->advertiser_id,
            'advertiser_name' => $connection?->advertiser_name,
            'identity_id' => $connection?->identity_id,
            'identity_name' => $connection?->identity_name,
            'identity_mode' => $connection?->identity_type === 'BC_AUTH_TT' ? 'spark' : ($connection?->identity_type ? 'non_spark' : null),
            'identity_type' => $connection?->identity_type,
            'tiktok_item_id' => $connection?->tiktok_item_id,
        ], fn (mixed $value): bool => filled($value));
    }

    private function panelHtml(array $draft): string
    {
        return view('partials.tiktok-campaign-panel', ['draft' => $draft])->render();
    }

    /** @param array<int, mixed> $answers */
    private function rememberAnswers(?Conversation $conversation, array $answers, ?TikTokConnection $connection): void
    {
        if ($conversation === null || $answers === []) {
            return;
        }

        $draft = json_decode((string) $conversation->tiktok_campaign_draft, true);
        $draft = is_array($draft) ? $draft : [];
        foreach ($answers as $answer) {
            if (! is_array($answer) || blank($answer['field'] ?? null) || blank($answer['value'] ?? null)) {
                continue;
            }
            $field = match ((string) $answer['field']) {
                'tiktok_advertiser_id' => 'advertiser_id',
                'campaign_objective' => 'objective',
                'budget' => ($draft['campaign_budget_optimization'] ?? null) === 'on' ? 'campaign_budget' : 'daily_budget',
                'country', 'location', 'location_ids' => 'location_id',
                'final_url' => 'landing_page_url',
                'post_id' => 'tiktok_item_id',
                'destination' => 'promotion_type',
                default => (string) $answer['field'],
            };
            $flowDraft = array_filter([
                ...$draft,
                'advertiser_id' => $connection?->advertiser_id,
                'identity_id' => $connection?->identity_id,
                'identity_mode' => $connection?->identity_type === 'BC_AUTH_TT' ? 'spark' : ($connection?->identity_type ? 'non_spark' : null),
            ], fn (mixed $value): bool => filled($value));
            $next = $this->flow->next($flowDraft);
            if (! array_key_exists($field, $draft) && $field !== ($next['field'] ?? null)) {
                continue;
            }
            $value = $answer['value'];
            if ($field !== 'advertiser_id' && $field !== 'identity_id') {
                $draft[$field] = is_array($value) ? implode(', ', $value) : $value;
            }
            if ($field === 'preview_confirmed' && strtolower((string) $value) !== 'yes') {
                unset($draft['validation_status'], $draft['preview_confirmed'], $draft['delivery_status']);
            }
            if ($field === 'delivery_status' && strtolower((string) $value) === 'draft') {
                $draft['delivery_confirmed'] = 'yes';
            }
            if ($field === 'delivery_status' && strtolower((string) $value) === 'paused') {
                $draft['delivery_confirmed'] = 'yes';
            }
        }

        $conversation->update(['tiktok_campaign_draft' => json_encode($draft)]);
    }

    private function offerFallbackChoices(string $reply): void
    {
        $choices = app(Choices::class);
        if ($choices->all() !== []) {
            return;
        }

        if (preg_match('/which countries|which country|countries or regions|country or region/i', $reply)) {
            $choices->offer(
                field: 'country',
                question: 'Which country should this TikTok campaign target?',
                options: [
                    ['value' => 'US', 'label' => 'United States', 'sends' => 'Set country to United States.'],
                    ['value' => 'CA', 'label' => 'Canada', 'sends' => 'Set country to Canada.'],
                    ['value' => 'GB', 'label' => 'United Kingdom', 'sends' => 'Set country to United Kingdom.'],
                    ['value' => '__other__', 'label' => 'Other', 'hint' => 'Choose another country', 'sends' => 'Set country to another country.'],
                ],
            );
        }
    }

    private function resolveConversation(Request $request): ?Conversation
    {
        if ($request->boolean('new')) {
            return null;
        }

        $id = $request->string('conversation')->toString();

        return $id !== '' ? $this->ownedConversation($request, $id) : null;
    }

    private function ownedConversation(Request $request, string $id): Conversation
    {
        $conversation = Conversation::find($id);

        abort_if(
            $conversation === null
            || $conversation->chat_type !== 'tiktok_ads'
            || $conversation->participant_type !== $request->user()?->getMorphClass()
            || (string) $conversation->participant_id !== (string) $request->user()?->getKey(),
            404,
        );

        return $conversation;
    }

    private function recentConversations(Request $request)
    {
        return Conversation::query()
            ->where('chat_type', 'tiktok_ads')
            ->where('participant_type', $request->user()?->getMorphClass())
            ->where('participant_id', $request->user()?->getKey())
            ->latest('updated_at')
            ->limit(30)
            ->get();
    }

    private function conversationTitle(?string $id): string
    {
        return trim((string) ($id ? Conversation::find($id)?->title : null)) ?: 'TikTok Ads chat';
    }

    private function transcript(?Conversation $conversation): array
    {
        if (! $conversation) {
            return [];
        }

        return DB::table('agent_conversation_messages')
            ->where('conversation_id', $conversation->id)
            ->whereIn('role', ['user', 'assistant'])
            ->orderBy('created_at')
            ->orderBy('id')
            ->get(['role', 'content'])
            ->filter(fn ($message): bool => trim((string) $message->content) !== '')
            ->map(fn ($message): array => [
                'role' => $message->role === 'user' ? 'user' : 'agent',
                'text' => (string) $message->content,
                'html' => $message->role === 'user' ? null : (string) Markdown::toHtml((string) $message->content),
            ])
            ->values()
            ->all();
    }

    private function rememberConversation(?string $conversationId): ?Conversation
    {
        if (blank($conversationId)) {
            return null;
        }

        $conversation = Conversation::find($conversationId);

        if (! $conversation) {
            return null;
        }

        $conversation->update(['chat_type' => 'tiktok_ads']);
        $conversation->touch();

        return $conversation;
    }

    private function recordFailedTurn(?string $conversationId, Request $request, string $message, string $reply): void
    {
        if (blank($conversationId)) {
            return;
        }

        $base = [
            'conversation_id' => $conversationId,
            'participant_type' => $request->user()?->getMorphClass(),
            'participant_id' => $request->user()?->getKey(),
            'agent' => TikTokAdsAgent::class,
            'attachments' => '[]',
            'tool_calls' => '[]',
            'tool_results' => '[]',
            'usage' => '[]',
            'meta' => '[]',
            'created_at' => now(),
            'updated_at' => now(),
        ];

        DB::table('agent_conversation_messages')->insert([
            [...$base, 'id' => (string) Str::uuid7(), 'role' => 'user', 'content' => $message],
            [...$base, 'id' => (string) Str::uuid7(), 'role' => 'assistant', 'content' => $reply],
        ]);
    }
}
