<?php

namespace App\Http\Controllers;

use App\Models\CatalogLandingPageVisit;
use App\Models\LandingPage;
use App\Models\PromptLandingPage;
use App\Models\PromptLandingPageLead;
use App\Services\CatalogLandingPageAffiliateDocument;
use App\Services\CatalogLandingPageTrackingDocument;
use App\Services\CatalogLandingPageVisitDocument;
use App\Services\PublicLandingPageTrackingDocument;
use App\Services\PublicPromptLandingPageDocument;
use App\Support\PromptLandingPageLeadPayload;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;

class PublicPromptLandingPageController extends Controller
{
    public function show(
        Request $request,
        string $domain,
        string $purpose,
        int $landing_page,
        PublicPromptLandingPageDocument $document,
        PublicLandingPageTrackingDocument $trackingDocument,
        CatalogLandingPageAffiliateDocument $catalogAffiliateDocument,
        CatalogLandingPageTrackingDocument $catalogTrackingDocument,
        CatalogLandingPageVisitDocument $catalogVisitDocument,
    ): Response {
        $utmSource = $request->query('utm_source');
        $utmSource = is_string($utmSource) ? $utmSource : null;
        $utmCampaign = $request->query('utm_campaign');
        $utmCampaign = is_string($utmCampaign) ? $utmCampaign : null;

        if (LandingPage::isCatalogPurposeSlug($purpose)) {
            $page = $this->resolveCatalogPage($domain, $purpose, $landing_page);
            $visit = $this->recordCatalogVisit($page, $request);
            $html = $catalogAffiliateDocument->apply($page, $utmSource, $utmCampaign);
            $html = $catalogVisitDocument->apply($page, $html, $visit);

            return response($catalogTrackingDocument->applyHtml(
                $page,
                $html,
                $utmSource,
            ), 200, [
                'Content-Type' => 'text/html; charset=UTF-8',
            ]);
        }

        $page = $this->resolvePage($domain, $purpose, $landing_page);
        $lead = $this->recordVisit($page, $request);

        return response($trackingDocument->apply(
            $document->render($page, $lead->page_load_id),
            $utmSource,
            $page->googleConversionSendTo(),
            $page->metaPixelId(),
        ), 200, [
            'Content-Type' => 'text/html; charset=UTF-8',
        ]);
    }

    public function storeLead(
        Request $request,
        string $domain,
        string $purpose,
        int $landing_page,
    ): JsonResponse|Response {
        abort_if(LandingPage::isCatalogPurposeSlug($purpose), 404);

        $page = $this->resolvePage($domain, $purpose, $landing_page);

        $pageLoadId = PromptLandingPageLeadPayload::pageLoadId($request);
        $payload = PromptLandingPageLeadPayload::fromRequest($request);
        $lead = $this->storeSubmission($page, $pageLoadId, $payload, $request);
        $created = $lead->wasRecentlyCreated;

        if ($request->expectsJson()) {
            return response()->json(['ok' => true], $created ? 201 : 200);
        }

        return response($this->thanksHtml($page), $created ? 201 : 200, [
            'Content-Type' => 'text/html; charset=UTF-8',
        ]);
    }

    public function storeDownloadClick(
        Request $request,
        string $domain,
        string $purpose,
        int $landing_page,
    ): JsonResponse {
        abort_if(LandingPage::isCatalogPurposeSlug($purpose), 404);

        $page = $this->resolvePage($domain, $purpose, $landing_page);

        abort_unless($page->isAppDownload(), 404);

        $pageLoadId = PromptLandingPageLeadPayload::pageLoadId($request);

        if ($pageLoadId === null) {
            throw ValidationException::withMessages([
                'page_load_id' => 'The page load id is required.',
            ]);
        }

        $destinationUrl = $this->downloadDestinationUrl($request);

        $visit = $page->leads()->where('page_load_id', $pageLoadId)->first();

        abort_if($visit === null, 404);

        $visit->clickouts()->create([
            'prompt_landing_page_id' => $page->id,
            'destination_url' => $destinationUrl,
        ]);

        return response()->json(['ok' => true], 201);
    }

    public function storeClickout(
        Request $request,
        string $domain,
        string $purpose,
        int $landing_page,
    ): JsonResponse {
        abort_unless(LandingPage::isCatalogPurposeSlug($purpose), 404);

        $page = $this->resolveCatalogPage($domain, $purpose, $landing_page);
        $pageLoadId = PromptLandingPageLeadPayload::pageLoadId($request);

        if ($pageLoadId === null) {
            throw ValidationException::withMessages([
                'page_load_id' => 'The page load id is required.',
            ]);
        }

        $destinationUrl = $request->input('destination_url');
        $destinationUrl = is_string($destinationUrl) ? trim($destinationUrl) : '';

        if ($destinationUrl === '' || filter_var($destinationUrl, FILTER_VALIDATE_URL) === false) {
            throw ValidationException::withMessages([
                'destination_url' => 'The destination URL is invalid.',
            ]);
        }

        $visit = $page->catalogVisits()->where('page_load_id', $pageLoadId)->first();

        abort_if($visit === null, 404);

        $visit->clickouts()->create([
            'landing_page_id' => $page->id,
            'destination_url' => Str::limit($destinationUrl, 2048, ''),
        ]);

        return response()->json(['ok' => true], 201);
    }

    private function recordCatalogVisit(LandingPage $page, Request $request): CatalogLandingPageVisit
    {
        $queryParams = PromptLandingPageLeadPayload::fromQueryArray($request->query());

        return $page->catalogVisits()->create([
            'page_load_id' => (string) Str::uuid(),
            'query_params' => $queryParams === [] ? null : $queryParams,
            'ip' => $request->ip(),
            'user_agent' => Str::limit((string) $request->userAgent(), 512, ''),
        ]);
    }

    private function recordVisit(PromptLandingPage $page, Request $request): PromptLandingPageLead
    {
        $queryParams = PromptLandingPageLeadPayload::fromQueryArray($request->query());

        return $page->leads()->create([
            'page_load_id' => (string) Str::uuid(),
            'payload' => [],
            'query_params' => $queryParams === [] ? null : $queryParams,
            'ip' => $request->ip(),
            'user_agent' => Str::limit((string) $request->userAgent(), 512, ''),
        ]);
    }

    /**
     * @param  array<string, mixed>  $payload
     */
    private function storeSubmission(
        PromptLandingPage $page,
        ?string $pageLoadId,
        array $payload,
        Request $request,
    ): PromptLandingPageLead {
        $attributes = [
            'ip' => $request->ip(),
            'user_agent' => Str::limit((string) $request->userAgent(), 512, ''),
            'payload' => $payload,
        ];

        if ($pageLoadId === null) {
            return $page->leads()->create($attributes);
        }

        $lead = $page->leads()->where('page_load_id', $pageLoadId)->first();

        if ($lead === null) {
            return $page->leads()->create([
                ...$attributes,
                'page_load_id' => $pageLoadId,
            ]);
        }

        $lead->fill([
            'ip' => $attributes['ip'],
            'user_agent' => $attributes['user_agent'],
            'payload' => array_replace(is_array($lead->payload) ? $lead->payload : [], $payload),
        ]);
        $lead->save();

        return $lead;
    }

    private function resolvePage(string $domain, string $purpose, int $id): PromptLandingPage
    {
        $page = PromptLandingPage::findPublic($domain, $purpose, $id);

        abort_if($page === null, 404);

        return $page;
    }

    private function downloadDestinationUrl(Request $request): string
    {
        $destinationUrl = $request->input('destination_url');
        $destinationUrl = is_string($destinationUrl) ? trim($destinationUrl) : '';

        if ($destinationUrl === '' || $destinationUrl === '#') {
            return '';
        }

        if (preg_match('/^(javascript|data|vbscript):/i', $destinationUrl) !== 0) {
            throw ValidationException::withMessages([
                'destination_url' => 'The destination URL is invalid.',
            ]);
        }

        return Str::limit($destinationUrl, 2048, '');
    }

    private function resolveCatalogPage(string $domain, string $purpose, int $id): LandingPage
    {
        $page = LandingPage::findPublic($domain, $purpose, $id);

        abort_if($page === null, 404);

        return $page;
    }

    private function thanksHtml(PromptLandingPage $page): string
    {
        $label = e($page->returnCtaLabel());

        return <<<HTML
<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Thanks</title>
</head>
<body>
    <p>Thanks — we received your details.</p>
    <p><a href="/" data-admedia-home="1">Return to {$label}</a></p>
</body>
</html>
HTML;
    }
}
