<?php

namespace App\Models;

use App\Support\PromptLandingPagePixels;
use Database\Factories\PromptLandingPageFactory;
use Illuminate\Database\Eloquent\Attributes\Fillable;
use Illuminate\Database\Eloquent\Attributes\Scope;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Str;

#[Fillable([
    'user_id', 'title', 'website_url', 'purpose', 'status', 'brief',
    'html', 'css', 'js', 'prompt_used', 'failure_reason',
    'approved_by', 'approved_at', 'rejected_at', 'rejection_reason',
    'google_conversion_send_to', 'meta_pixel_id',
])]
class PromptLandingPage extends Model
{
    /** @use HasFactory<PromptLandingPageFactory> */
    use HasFactory;

    public const PURPOSE_APP_DOWNLOAD = 'App download page';

    /**
     * @var list<string>
     */
    public const PURPOSES = [
        'Leads Page',
        self::PURPOSE_APP_DOWNLOAD,
        'Signup page',
    ];

    public const UNSET_PURPOSE = 'unset';

    /**
     * @var array<string, string>
     */
    public const PARTNER_PREVIEW_HOSTS = [
        'webcrawlers.com' => 'dev.webcrawlers.com',
        'attestpath.ai' => 'dev.attestpath.ai',
        'rateit.com' => '192.168.30.106/php74/johnty/rateit_latest.com/html/public/?',
    ];

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'brief' => 'array',
            'approved_at' => 'datetime',
            'rejected_at' => 'datetime',
        ];
    }

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function approvedBy(): BelongsTo
    {
        return $this->belongsTo(User::class, 'approved_by');
    }

    public function messages(): HasMany
    {
        return $this->hasMany(PromptLandingPageMessage::class)->orderBy('id');
    }

    public function attachments(): HasMany
    {
        return $this->hasMany(PromptLandingPageAttachment::class);
    }

    public function leads(): HasMany
    {
        return $this->hasMany(PromptLandingPageLead::class)->latest('id');
    }

    public function clickouts(): HasMany
    {
        return $this->hasMany(PromptLandingPageClickout::class);
    }

    public function versions(): HasMany
    {
        return $this->hasMany(PromptLandingPageVersion::class)->orderByDesc('version');
    }

    public function editorDraft(): HasOne
    {
        return $this->hasOne(PromptLandingPageEditorDraft::class);
    }

    protected static function booted(): void
    {
        static::deleting(function (PromptLandingPage $page): void {
            $page->attachments->each->deleteFile();
        });
    }

    public function hasSetup(): bool
    {
        return filled($this->website_url) && filled($this->purpose);
    }

    public function isAppDownload(): bool
    {
        return $this->purpose === self::PURPOSE_APP_DOWNLOAD;
    }

    public function displayWebsite(): string
    {
        return $this->publicHost() ?? (filled($this->website_url) ? (string) $this->website_url : '—');
    }

    public function returnCtaLabel(): string
    {
        $brief = $this->brief;
        $brand = is_array($brief) ? trim((string) ($brief['brand_name'] ?? '')) : '';

        if ($brand !== '') {
            return $brand;
        }

        return $this->publicHost() ?? 'home';
    }

    public static function normalizeHost(string $host): string
    {
        $host = strtolower(trim($host));
        $host = preg_replace('/:\d+$/', '', $host) ?? $host;

        if (str_starts_with($host, 'www.')) {
            $host = substr($host, 4);
        }

        return $host;
    }

    public function publicHost(): ?string
    {
        $host = parse_url((string) $this->website_url, PHP_URL_HOST);

        return is_string($host) && $host !== '' ? self::normalizeHost($host) : null;
    }

    public function purposeSlug(): string
    {
        return Str::slug((string) $this->purpose);
    }

    public static function purposeFromSlug(string $slug): ?string
    {
        foreach (self::PURPOSES as $purpose) {
            if (Str::slug($purpose) === $slug) {
                return $purpose;
            }
        }

        return null;
    }

    public function isPubliclyServable(): bool
    {
        return $this->isApproved() && $this->isReady();
    }

    /**
     * @return array{domain: string, purpose: string, landing_page: int}|null
     */
    public function publicRouteParameters(): ?array
    {
        $host = $this->publicHost();
        $purpose = $this->purposeSlug();

        if ($host === null || $purpose === '' || $this->id === null) {
            return null;
        }

        return [
            'domain' => $host,
            'purpose' => $purpose,
            'landing_page' => $this->id,
        ];
    }

    public function publicUrl(): ?string
    {
        if (! $this->isPubliclyServable()) {
            return null;
        }

        $parameters = $this->publicRouteParameters();

        return $parameters === null ? null : route('public-prompt-landing-pages.show', $parameters);
    }

    public function partnerPreviewUrl(): ?string
    {
        if (! $this->isApproved()) {
            return null;
        }

        $host = $this->publicHost();
        $purpose = $this->purposeSlug();
        $previewHost = is_string($host) ? (self::PARTNER_PREVIEW_HOSTS[$host] ?? null) : null;

        if ($previewHost === null || $purpose === '' || $this->id === null) {
            return null;
        }

        return 'https://'.$previewHost.'/lp/'.$purpose.'/'.$this->id;
    }

    public function googleConversionSendTo(): ?string
    {
        $value = trim((string) ($this->google_conversion_send_to ?? ''));

        return PromptLandingPagePixels::isGoogleSendTo($value) ? $value : null;
    }

    public function metaPixelId(): ?string
    {
        $value = trim((string) ($this->meta_pixel_id ?? ''));

        return PromptLandingPagePixels::isMetaPixelId($value) ? $value : null;
    }

    public function leadCaptureUrl(): ?string
    {
        if (! $this->isPubliclyServable()) {
            return null;
        }

        $parameters = $this->publicRouteParameters();

        return $parameters === null ? null : route('public-prompt-landing-pages.leads.store', $parameters);
    }

    public function downloadClickoutUrl(): ?string
    {
        if (! $this->isPubliclyServable() || ! $this->isAppDownload()) {
            return null;
        }

        $parameters = $this->publicRouteParameters();

        return $parameters === null
            ? null
            : route('public-prompt-landing-pages.downloads.store', $parameters, absolute: true);
    }

    public static function findPublic(string $domain, string $purposeSlug, int $id): ?self
    {
        $purpose = self::purposeFromSlug($purposeSlug);

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

        $page = self::query()->find($id);

        if ($page === null || ! $page->isPubliclyServable() || $page->purpose !== $purpose) {
            return null;
        }

        if ($page->publicHost() !== self::normalizeHost($domain)) {
            return null;
        }

        return $page;
    }

    public static function purposeTabLabel(string $tab): string
    {
        return $tab === self::UNSET_PURPOSE ? 'Unset' : $tab;
    }

    #[Scope]
    protected function forPurposeTab(Builder $query, string $purpose): Builder
    {
        if ($purpose === self::UNSET_PURPOSE) {
            return $query->where(function (Builder $inner): void {
                $inner->whereNull('purpose')->orWhere('purpose', '');
            });
        }

        return $query->where('purpose', $purpose);
    }

    public static function normalizeWebsiteUrl(?string $value): ?string
    {
        $value = trim((string) $value);

        if ($value === '') {
            return null;
        }

        if (preg_match('#^[a-z][a-z0-9+.-]*://#i', $value) !== 1) {
            $value = 'https://'.$value;
        }

        $parts = parse_url($value);

        if (! is_array($parts) || empty($parts['host'])) {
            return null;
        }

        $scheme = strtolower((string) ($parts['scheme'] ?? 'https'));

        if (! in_array($scheme, ['http', 'https'], true)) {
            return null;
        }

        $host = strtolower($parts['host']);

        if ($host !== 'localhost' && ! str_contains($host, '.')) {
            return null;
        }

        $port = isset($parts['port']) ? ':'.$parts['port'] : '';

        return $scheme.'://'.$host.$port;
    }

    public function isReady(): bool
    {
        return filled($this->html);
    }

    public function canEdit(): bool
    {
        return $this->isReady() && ! $this->isBusy() && ! $this->isApproved();
    }

    public function canDuplicate(): bool
    {
        return $this->isReady() && ! $this->isBusy();
    }

    public function canReceiveOverride(): bool
    {
        return ! $this->isBusy();
    }

    public function isBusy(): bool
    {
        return in_array($this->status, ['queued', 'searching', 'analysing', 'generating'], true);
    }

    public function isPendingApproval(): bool
    {
        return in_array($this->status, ['pending_approval', 'completed'], true);
    }

    public function promoteLegacyCompleted(): void
    {
        if ($this->status !== 'completed' || ! $this->isReady()) {
            return;
        }

        $this->update(['status' => 'pending_approval']);
    }

    public function isApproved(): bool
    {
        return $this->status === 'approved';
    }

    public function isRejected(): bool
    {
        return $this->status === 'rejected';
    }

    public function canApprove(): bool
    {
        return $this->isPendingApproval() && $this->isReady();
    }

    public function canDownload(): bool
    {
        return $this->isApproved() && $this->isReady();
    }

    public function composerLocked(): bool
    {
        return $this->isApproved() || ! $this->hasSetup();
    }

    public function approve(User $actor): void
    {
        $this->update([
            'status' => 'approved',
            'approved_by' => $actor->id,
            'approved_at' => now(),
            'rejected_at' => null,
            'rejection_reason' => null,
        ]);
    }

    public function reject(?string $reason = null): void
    {
        $this->update([
            'status' => 'rejected',
            'rejected_at' => now(),
            'rejection_reason' => $reason,
            'approved_by' => null,
            'approved_at' => null,
        ]);
    }

    public function statusLabel(): string
    {
        return match ($this->status) {
            'queued', 'searching' => 'Searching…',
            'analysing' => 'Analysing…',
            'generating' => 'Generating…',
            'pending_approval', 'completed' => 'Waiting for approval',
            'approved' => 'Approved',
            'rejected' => 'Rejected',
            'failed' => 'Something went wrong.',
            default => '',
        };
    }

    public function displayTitle(): string
    {
        return trim((string) $this->title) !== '' ? (string) $this->title : 'Untitled';
    }

    public function renderedDocument(): string
    {
        return $this->assembleDocument($this->html ?? '', $this->css ?? '', $this->js ?? '');
    }

    public function assembleDocument(string $html, string $css, string $js): string
    {
        if ($this->isFullHtmlDocument($html)) {
            return $this->withInPageAnchorScrolling(
                $this->withFaviconLink($this->previewFullDocument($html, $css, $js)),
            );
        }

        $title = e($this->title ?: 'Landing page');
        [$bodyAttributes, $innerHtml] = $this->splitStoredHtml($html);

        return $this->withInPageAnchorScrolling(
            $this->withFaviconLink(
                '<!doctype html>'."\n"
                .'<html>'."\n"
                .'<head>'."\n"
                .'<meta charset="utf-8">'."\n"
                .'<meta name="viewport" content="width=device-width, initial-scale=1">'."\n"
                .'<title>'.$title.'</title>'."\n"
                .'<style>'."\n".$this->assembledCss($css)."\n".'</style>'."\n"
                .'</head>'."\n"
                .'<body'.$bodyAttributes.'>'."\n"
                .$innerHtml."\n"
                .'<script>'."\n".$js."\n".'</script>'."\n"
                .'</body>'."\n"
                .'</html>'
            ),
        );
    }

    private function isFullHtmlDocument(string $html): bool
    {
        return (bool) preg_match('/<!doctype html>|<html[\s>]/i', $html);
    }

    private function previewFullDocument(string $html, string $css, string $js): string
    {
        $html = $this->ensureViewport($html);
        $css = $this->assembledCss($css);
        $js = trim($js);

        if ($css !== '' && preg_match('/<\/head>/i', $html) === 1) {
            $html = preg_replace(
                '/<\/head>/i',
                "<style>\n{$css}\n</style>\n</head>",
                $html,
                1,
            ) ?? $html;
        }

        if ($js !== '' && preg_match('/<\/body>/i', $html) === 1) {
            $html = preg_replace(
                '/<\/body>/i',
                "<script>\n{$js}\n</script>\n</body>",
                $html,
                1,
            ) ?? $html;
        }

        return $html;
    }

    private function withFaviconLink(string $html): string
    {
        $url = $this->faviconHref();

        if ($url === null) {
            return $html;
        }

        if (preg_match('/<link\b[^>]*rel=["\'][^"\']*icon[^"\']*["\'][^>]*>/i', $html) === 1) {
            return $html;
        }

        $tag = '<link rel="icon" href="'.e($url).'">';

        if (preg_match('/<\/head>/i', $html) === 1) {
            return preg_replace('/<\/head>/i', $tag."\n</head>", $html, 1) ?? $html;
        }

        return $html;
    }

    private function faviconHref(): ?string
    {
        $brief = $this->brief;

        if (! is_array($brief)) {
            return null;
        }

        $url = trim((string) ($brief['favicon_url'] ?? ''));

        if ($url === '' || ! Str::startsWith($url, ['http://', 'https://'])) {
            return null;
        }

        return $url;
    }

    private function assembledCss(string $css): string
    {
        return trim($this->responsiveBaseCss()."\n".$css);
    }

    private function ensureViewport(string $html): string
    {
        if (preg_match('/name=["\']viewport["\']/i', $html) === 1) {
            return $html;
        }

        $tag = '<meta name="viewport" content="width=device-width, initial-scale=1">';

        if (preg_match('/<meta charset=[^>]*>/i', $html) === 1) {
            return preg_replace(
                '/<meta charset=[^>]*>/i',
                '$0'."\n".$tag,
                $html,
                1,
            ) ?? $html;
        }

        if (preg_match('/<head[^>]*>/i', $html) === 1) {
            return preg_replace(
                '/<head[^>]*>/i',
                '$0'."\n".$tag,
                $html,
                1,
            ) ?? $html;
        }

        return $html;
    }

    private function responsiveBaseCss(): string
    {
        return <<<'CSS'
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; height: auto !important; min-height: 100%; overflow-x: hidden; overflow-x: clip; overflow-y: auto !important; scroll-padding-top: 6rem; }
body { margin: 0; max-width: 100%; height: auto !important; min-height: 100%; overflow-x: hidden; overflow-x: clip; overflow-y: visible !important; }
[id] { scroll-margin-top: 6rem; }
img, video, canvas, svg, iframe { max-width: 100%; height: auto; }
@media (max-width: 991px) {
  nav, header { flex-wrap: wrap; }
}
@media (max-width: 767px) {
  body { word-wrap: break-word; overflow-wrap: anywhere; }
}
CSS;
    }

    /**
     * Keep in-page hash CTAs on the window scrollport so generated
     * html/body overflow:hidden pages remain scrollable after a jump.
     */
    private function withInPageAnchorScrolling(string $html): string
    {
        $script = '<script>'."\n".$this->inPageAnchorScrollScript()."\n".'</script>';

        if (preg_match('/<\/body>/i', $html) === 1) {
            return preg_replace('/<\/body>/i', $script."\n</body>", $html, 1) ?? $html;
        }

        return $html.$script;
    }

    private function inPageAnchorScrollScript(): string
    {
        return <<<'JS'
(function () {
    function unlockWindowScroll() {
        document.documentElement.style.setProperty('height', 'auto', 'important');
        document.documentElement.style.setProperty('overflow-y', 'auto', 'important');
        if (document.body) {
            document.body.style.setProperty('height', 'auto', 'important');
            document.body.style.setProperty('overflow-y', 'visible', 'important');
        }
    }

    function headerOffset() {
        var header = document.querySelector('header');
        if (!header) {
            return 0;
        }
        var position = window.getComputedStyle(header).position;
        if (position !== 'sticky' && position !== 'fixed') {
            return 0;
        }
        return Math.ceil(header.getBoundingClientRect().height);
    }

    function scrollToHash(id, behavior) {
        unlockWindowScroll();
        if (!id) {
            window.scrollTo({ top: 0, left: 0, behavior: behavior });
            return true;
        }
        var target = document.getElementById(id);
        if (!target) {
            return false;
        }
        var top = target.getBoundingClientRect().top + window.pageYOffset - headerOffset() - 8;
        window.scrollTo({ top: Math.max(0, top), left: 0, behavior: behavior });
        return true;
    }

    unlockWindowScroll();

    document.addEventListener('click', function (event) {
        var node = event.target;
        if (node && node.nodeType !== 1) {
            node = node.parentElement;
        }
        var link = node && node.closest ? node.closest('a[href]') : null;
        if (!link) {
            return;
        }
        var href = link.getAttribute('href');
        if (!href || href.indexOf('#') === -1) {
            return;
        }
        var url;
        try {
            url = new URL(link.href, window.location.href);
        } catch (err) {
            return;
        }
        if (url.pathname !== window.location.pathname || url.search !== window.location.search) {
            return;
        }
        var id = url.hash.replace(/^#/, '');
        if (id && !document.getElementById(id)) {
            return;
        }
        event.preventDefault();
        event.stopPropagation();
        scrollToHash(id, 'smooth');
        if (history.replaceState) {
            history.replaceState(null, '', id ? url.hash : window.location.pathname + window.location.search);
        }
    }, true);

    window.addEventListener('hashchange', function () {
        scrollToHash(window.location.hash.replace(/^#/, ''), 'smooth');
    });

    if (window.location.hash) {
        scrollToHash(window.location.hash.replace(/^#/, ''), 'auto');
    }
})();
JS;
    }

    /**
     * @return array{0: string, 1: string}
     */
    private function splitStoredHtml(string $html): array
    {
        if (preg_match('/<body([^>]*)>(.*)<\/body>/is', $html, $matches) !== 1) {
            return ['', $html];
        }

        $attributes = trim($matches[1]);

        return [$attributes === '' ? '' : ' '.$attributes, trim($matches[2])];
    }
}
