<?php

namespace App\Crm;

use App\LeadExports\LeadExportFieldType;
use App\Models\LeadExport;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Http;
use Mtc\Crm\Contracts\EnquiryAction;
use Mtc\Crm\Contracts\EnquiryActionModel;
use Mtc\Crm\Contracts\EnquiryModel;
use Mtc\MercuryDataModels\ApiNotification;
use Mtc\MercuryDataModels\Vehicle;

/**
 * Generic delivery handler for UI-configurable CRM lead exports. Unlike the fixed
 * integrations it is not registered in config('crm.form_actions'); it is resolved by
 * ProcessIncomingEnquiryJob for any action named "lead-export:<id>" and parameterises
 * itself from that action row (see LeadExport::fromProviderKey).
 *
 * Payload shape follows the export's mode: PASSTHROUGH sends each answer under its
 * form field name; MAPPED sends answers (plus the attached vehicle-of-interest)
 * against the configured destination paths, nesting dot-notation into JSON objects.
 *
 * Marketing attribution (UTM parameters, gclid, landing-page URL) is enquiry
 * metadata rather than a form answer, so both modes attach it under a dedicated
 * "marketing" object, sourced from the enquiry the same way the fixed integrations do.
 */
class SendLeadExportNotification implements EnquiryAction
{
    use EnquiryDataToProviderData;

    private ?string $failure = null;

    private ?int $status = null;

    /**
     * Data fields to add to form.
     */
    public function formAdditionalDataFields(): array
    {
        return [];
    }

    /**
     * Data fields to add to questions. Per-question mapping options are built per map
     * in FormViewResource, so nothing static is contributed here.
     */
    public function formQuestionDataFields(): array
    {
        return [];
    }

    /**
     * Whether the integration is available. Lead exports are gated per map by their
     * active flag rather than a global setting.
     */
    public function enabled(): bool
    {
        return true;
    }

    public function handle(EnquiryActionModel $action, EnquiryModel $enquiry): ?bool
    {
        $leadExport = LeadExport::fromProviderKey((string) $action->action_name);

        if (!$leadExport || !$leadExport->active) {
            $this->failure = 'Lead export is missing or inactive.';
            return false;
        }

        if (empty($leadExport->url)) {
            $this->failure = 'No destination URL is configured for this lead export.';
            return false;
        }

        $payload = $this->buildPayload($leadExport, $enquiry);

        $response = Http::withHeaders($this->headers($leadExport))
            ->asJson()
            ->post($leadExport->url, $payload);

        $this->status = $response->status();

        ApiNotification::query()->create([
            'provider' => $leadExport->providerKey(),
            'reference' => $enquiry->id,
            'processed' => $response->successful(),
            'data' => [
                'request' => $payload,
                'result' => $response->json() ?? $response->body(),
            ],
        ]);

        if (!$response->successful()) {
            $this->failure = 'CRM responded with HTTP ' . $response->status() . '.';
            return false;
        }

        return true;
    }

    public function successDetails(): string
    {
        return 'Lead delivered (HTTP ' . ($this->status ?? 200) . ').';
    }

    public function failureReason(): string
    {
        return $this->failure ?? 'Lead export failed.';
    }

    /**
     * Build the outbound payload for the export's configured mode.
     *
     * @return array<string, mixed>
     */
    private function buildPayload(LeadExport $leadExport, EnquiryModel $enquiry): array
    {
        if (!$leadExport->isMapped()) {
            return $this->passthroughPayload($enquiry);
        }

        $params = $this->convertEnquiryWithObjectsToParams($enquiry, $leadExport->providerKey());

        // Attribution is enquiry metadata, not a form answer, so it is injected onto
        // fixed marketing.* destinations (mirroring how the vehicle-of-interest is mapped).
        $params = array_merge($params, $this->mapMarketing($enquiry));

        $params = $this->coerceValues($leadExport, $params);

        // Expand dot-notation destinations (e.g. "contact.firstName") into nested JSON.
        return Arr::undot($params);
    }

    /**
     * Answers keyed by their form field name, for the passthrough mode.
     *
     * @return array<string, mixed>
     */
    private function passthroughPayload(EnquiryModel $enquiry): array
    {
        $answers = $enquiry->getNormalizedAnswers()
            ->filter(fn ($item) => !empty($item['question']))
            ->mapWithKeys(fn ($item) => [$item['question'] => $item['answer']])
            ->all();

        // Attribution can't be a form answer, so it is appended under its own
        // "marketing" key rather than flattened in among the form field names.
        $marketing = $this->marketingData($enquiry);
        if (!empty($marketing)) {
            $answers['marketing'] = $marketing;
        }

        return $answers;
    }

    /**
     * Coerce each mapped value to its configured field type.
     *
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    private function coerceValues(LeadExport $leadExport, array $params): array
    {
        $types = collect($leadExport->field_map ?? [])
            ->filter(fn ($field) => !empty($field['destination']))
            ->mapWithKeys(fn ($field) => [$field['destination'] => $field['type'] ?? LeadExportFieldType::STRING])
            ->all();

        foreach ($params as $destination => $value) {
            $params[$destination] = LeadExportFieldType::coerce($value, $types[$destination] ?? LeadExportFieldType::STRING);
        }

        return $params;
    }

    /**
     * Request headers: the configured custom headers plus a bearer token when a
     * secret is stored on the export.
     *
     * @return array<string, string>
     */
    private function headers(LeadExport $leadExport): array
    {
        $headers = collect($leadExport->headers ?? [])
            ->filter(fn ($value) => is_string($value) || is_numeric($value))
            ->map(fn ($value) => (string) $value)
            ->all();

        if (!empty($leadExport->password) && !isset($headers['Authorization'])) {
            $headers['Authorization'] = 'Bearer ' . $leadExport->password;
        }

        return $headers;
    }

    /**
     * Marketing attribution captured on the enquiry (never a form answer): the UTM
     * parameters, the Google click id and the full landing-page URL. Sourced the same
     * way as the fixed integrations (SalesforceCrmApi). Empty values are dropped so
     * absent attribution never adds noise to the payload.
     *
     * @return array<string, string>
     */
    private function marketingData(EnquiryModel $enquiry): array
    {
        return array_filter([
            'utm_source' => data_get($enquiry->data, 'meta.utm_source'),
            'utm_medium' => data_get($enquiry->data, 'meta.utm_medium'),
            'utm_campaign' => data_get($enquiry->data, 'meta.utm_campaign'),
            'utm_term' => data_get($enquiry->data, 'meta.utm_term'),
            'utm_content' => data_get($enquiry->data, 'meta.utm_content'),
            'gclid' => data_get($enquiry->data, 'meta.gclid'),
            'landing_page' => $enquiry->source,
        ], fn ($value) => $value !== null && $value !== '');
    }

    /**
     * Marketing attribution keyed against the fixed marketing.* destinations used by
     * the default lead template, for mapped-mode payloads. Exports whose field_map
     * omits these paths simply ignore the extra keys.
     *
     * @return array<string, string>
     */
    private function mapMarketing(EnquiryModel $enquiry): array
    {
        $marketing = [];
        foreach ($this->marketingData($enquiry) as $key => $value) {
            $marketing['marketing.' . $key] = $value;
        }

        return $marketing;
    }

    /**
     * Map an attached vehicle-of-interest onto the standard destination paths used by
     * the default lead template. Exports whose field_map omits these paths simply
     * ignore the extra keys.
     *
     * @return array<string, mixed>
     */
    protected function mapVehicle(?Vehicle $vehicle): array
    {
        if (!$vehicle) {
            return [];
        }

        return array_filter([
            'vehicle.make' => $vehicle->make?->name,
            'vehicle.model' => $vehicle->model?->name,
            'vehicle.derivative' => $vehicle->derivative,
            'vehicle.registration' => $vehicle->registration_number,
            'vehicle.vin' => $vehicle->vin,
            'vehicle.price' => $vehicle->price,
            'vehicle.fuel_type' => $vehicle->fuelType?->name,
            'vehicle.transmission' => $vehicle->transmissionType?->name,
            'vehicle.year' => $vehicle->manufacture_year,
        ], fn ($value) => $value !== null);
    }
}
