<?php

namespace App\Services\Meta;

use App\Models\MetaOrderAttribution;
use App\Models\Order;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Schema;

class MetaOrderAttributionService
{
    private const META_SOURCES = ['facebook', 'fb', 'instagram', 'ig', 'meta'];

    public function capture(Order $order, Request $request): ?MetaOrderAttribution
    {
        if (! Schema::hasTable('meta_order_attributions')) {
            return null;
        }

        $payload = $this->normalizePayload($request->input('meta_attribution'));
        $payload ??= $this->normalizePayload($this->payloadFromRequest($request));
        if ($payload === null) {
            return null;
        }

        return MetaOrderAttribution::query()->updateOrCreate(
            ['order_id' => $order->id],
            $payload
        );
    }

    public function normalizePayload(mixed $input): ?array
    {
        if (is_string($input)) {
            $input = json_decode($input, true);
        }

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

        $source = strtolower($this->text(data_get($input, 'utm_source'), 100) ?? '');
        $campaignId = $this->identifier(data_get($input, 'campaign_id'));
        $adsetId = $this->identifier(data_get($input, 'adset_id'));
        $adId = $this->identifier(data_get($input, 'ad_id'));
        $fbclid = $this->text(data_get($input, 'fbclid'), 500);
        $fbc = $this->text(data_get($input, 'fbc'), 255);
        $hasMetaSignal = in_array($source, self::META_SOURCES, true)
            || $campaignId !== null
            || $adsetId !== null
            || $adId !== null
            || $fbclid !== null
            || $fbc !== null;

        if (! $hasMetaSignal) {
            return null;
        }

        $capturedAt = $this->capturedAt(data_get($input, 'captured_at'));

        return [
            'campaign_id' => $campaignId,
            'adset_id' => $adsetId,
            'ad_id' => $adId,
            'product_id' => $this->identifier(data_get($input, 'product_id')),
            'utm_source' => $source !== '' ? $source : null,
            'utm_medium' => $this->text(data_get($input, 'utm_medium'), 100),
            'utm_campaign' => $this->text(data_get($input, 'utm_campaign'), 191),
            'utm_content' => $this->text(data_get($input, 'utm_content'), 191),
            'utm_term' => $this->text(data_get($input, 'utm_term'), 191),
            'fbclid' => $fbclid,
            'fbc' => $fbc,
            'fbp' => $this->text(data_get($input, 'fbp'), 255),
            'landing_url' => $this->url(data_get($input, 'landing_url')),
            'attribution_window_days' => 7,
            'attributed_at' => $capturedAt,
        ];
    }

    private function payloadFromRequest(Request $request): array
    {
        $landingUrl = $this->text(
            $request->input('landing_url') ?: $request->headers->get('referer'),
            2048
        );
        $query = [];

        if ($landingUrl !== null && filter_var($landingUrl, FILTER_VALIDATE_URL)) {
            parse_str((string) parse_url($landingUrl, PHP_URL_QUERY), $query);
        }

        return [
            'campaign_id' => $request->input('campaign_id')
                ?: $request->input('utm_campaign_id')
                ?: $request->input('meta_campaign_id')
                ?: data_get($query, 'campaign_id')
                ?: data_get($query, 'utm_campaign_id'),
            'adset_id' => $request->input('adset_id')
                ?: $request->input('utm_adset_id')
                ?: $request->input('meta_adset_id')
                ?: data_get($query, 'adset_id')
                ?: data_get($query, 'utm_adset_id'),
            'ad_id' => $request->input('ad_id')
                ?: $request->input('utm_ad_id')
                ?: $request->input('meta_ad_id')
                ?: data_get($query, 'ad_id')
                ?: data_get($query, 'utm_ad_id'),
            'product_id' => $request->input('product_id')
                ?: $request->input('retailer_id')
                ?: $request->input('utm_product_id')
                ?: data_get($query, 'product_id')
                ?: data_get($query, 'retailer_id')
                ?: data_get($query, 'utm_product_id'),
            'utm_source' => $request->input('utm_source') ?: data_get($query, 'utm_source'),
            'utm_medium' => $request->input('utm_medium') ?: data_get($query, 'utm_medium'),
            'utm_campaign' => $request->input('utm_campaign') ?: data_get($query, 'utm_campaign'),
            'utm_content' => $request->input('utm_content') ?: data_get($query, 'utm_content'),
            'utm_term' => $request->input('utm_term') ?: data_get($query, 'utm_term'),
            'fbclid' => $request->input('fbclid') ?: data_get($query, 'fbclid'),
            'fbc' => $request->input('fbc')
                ?: data_get($request->input('user_data', []), 'fbc')
                ?: $request->cookie('_fbc'),
            'fbp' => $request->input('fbp')
                ?: data_get($request->input('user_data', []), 'fbp')
                ?: $request->cookie('_fbp'),
            'landing_url' => $landingUrl,
            'captured_at' => $request->input('captured_at') ?: now()->toIso8601String(),
        ];
    }

    private function identifier(mixed $value): ?string
    {
        $value = $this->text($value, 100);

        return $value !== null && preg_match('/^[A-Za-z0-9_.:-]+$/', $value) === 1
            ? $value
            : null;
    }

    private function text(mixed $value, int $maxLength): ?string
    {
        $value = trim((string) $value);
        if ($value === '') {
            return null;
        }

        return mb_substr(strip_tags($value), 0, $maxLength);
    }

    private function url(mixed $value): ?string
    {
        $value = $this->text($value, 2048);

        return $value !== null && filter_var($value, FILTER_VALIDATE_URL)
            ? $value
            : null;
    }

    private function capturedAt(mixed $value): Carbon
    {
        try {
            $capturedAt = Carbon::parse((string) $value);
        } catch (\Throwable) {
            return now();
        }

        if ($capturedAt->isFuture() || $capturedAt->lt(now()->subDays(28))) {
            return now();
        }

        return $capturedAt;
    }
}
