<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\Controller;

use Doctrine\Persistence\ManagerRegistry;
use Mautic\CoreBundle\Controller\CommonController;
use Mautic\LeadBundle\Entity\Company;
use Mautic\LeadBundle\Entity\Lead;
use MauticPlugin\DealFlowBundle\Entity\Deal;
use MauticPlugin\DealFlowBundle\Entity\DealRepository;
use MauticPlugin\DealFlowBundle\Entity\InboundWebhookSource;
use MauticPlugin\DealFlowBundle\Entity\InboundWebhookSourceRepository;
use MauticPlugin\DealFlowBundle\Service\DealContextBuilder;
use MauticPlugin\DealFlowBundle\Service\DealManager;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;

final class WebhookController extends CommonController
{
    public function inboundAction(
        Request $request,
        ParameterBagInterface $parameterBag,
        DealRepository $dealRepository,
        InboundWebhookSourceRepository $inboundWebhookSourceRepository,
        DealManager $dealManager,
        DealContextBuilder $contextBuilder,
        ManagerRegistry $managerRegistry,
        ?string $sourceKey = null,
    ): JsonResponse {
        $auth = $this->resolveAuthorizedWebhookSource(
            $request,
            $sourceKey,
            $inboundWebhookSourceRepository,
            (string) $parameterBag->get('mautic.dealflow_inbound_webhook_secret')
        );

        if (null === $auth) {
            return new JsonResponse(['error' => 'Unauthorized'], 401);
        }

        $payload = json_decode($request->getContent(), true);
        if (!is_array($payload)) {
            return new JsonResponse(['error' => 'Invalid JSON payload'], 400);
        }

        $matchedDeal = $this->resolveDeal($payload, $dealRepository, $managerRegistry);
        if (is_array($matchedDeal)) {
            return new JsonResponse(['error' => 'Multiple matching deals found'], 409);
        }

        $dealData = [
            'pipeline_id' => $payload['pipeline_id'] ?? null,
            'title' => $payload['title'] ?? 'Inbound deal',
            'external_ref' => $payload['external_ref'] ?? null,
            'company_id' => $this->resolveCompanyId($payload, $managerRegistry),
            'owner_id' => $payload['owner_id'] ?? null,
            'stage_id' => $payload['stage_id'] ?? null,
            'stage_slug' => $payload['stage_slug'] ?? null,
            'amount' => $payload['amount'] ?? null,
            'currency' => $payload['currency'] ?? null,
            'confidence' => $payload['confidence'] ?? null,
            'source' => $payload['source'] ?? 'webhook',
            'expected_close_date' => $payload['expected_close_date'] ?? null,
            'description' => $payload['description'] ?? null,
        ];

        $deal = $matchedDeal instanceof Deal
            ? $dealManager->updateDealFromArray($matchedDeal, $dealData, null, 'webhook')
            : $dealManager->createDealFromArray($dealData, null, 'webhook');

        foreach ($payload['contacts'] ?? [] as $contact) {
            if (!is_array($contact)) {
                continue;
            }

            $contactId = $this->resolveContactId($contact, $managerRegistry);
            if (null !== $contactId) {
                $dealManager->attachContact($deal, $contactId, $contact['label'] ?? null, null, 'webhook');
            }
        }

        if (!empty($payload['note_body']) || !empty($payload['meeting_url'])) {
            $noteBody = trim((string) ($payload['note_body'] ?? ''));
            if (!empty($payload['meeting_url'])) {
                $noteBody = trim($noteBody."\n".'Meeting: '.$payload['meeting_url']);
            }

            if ('' !== $noteBody) {
                $dealManager->addNote($deal, $noteBody, null, 'webhook', 'webhook', $payload['source_ref'] ?? null);
            }
        }

        if (!empty($payload['task']) && is_array($payload['task'])) {
            $dealManager->addTask($deal, $payload['task'], null, 'webhook');
        }

        if ($auth['source'] instanceof InboundWebhookSource) {
            $sourceManager = $managerRegistry->getManagerForClass(InboundWebhookSource::class) ?? $managerRegistry->getManager();
            $auth['source']->setLastUsedAt(new \DateTimeImmutable());
            $sourceManager->persist($auth['source']);
            $sourceManager->flush();
        }

        return new JsonResponse([
            'deal' => $contextBuilder->buildWebhookPayload($deal, 'deal.webhook_processed', 'webhook', null, null),
            'webhook_source' => [
                'name' => $auth['source']?->getName() ?? 'Legacy fallback secret',
                'source_key' => $auth['source']?->getSourceKey(),
                'signature_format' => $auth['signatureFormat'],
            ],
        ]);
    }

    /**
     * @param array<string, mixed> $payload
     */
    private function resolveDeal(array $payload, DealRepository $dealRepository, ManagerRegistry $managerRegistry): Deal|array|null
    {
        if (!empty($payload['deal_id'])) {
            return $dealRepository->find((int) $payload['deal_id']);
        }

        if (!empty($payload['external_ref'])) {
            return $dealRepository->findOneByExternalRef((string) $payload['external_ref']);
        }

        $contactIds = [];
        foreach ($payload['contacts'] ?? [] as $contact) {
            if (!is_array($contact)) {
                continue;
            }

            $contactId = $this->resolveContactId($contact, $managerRegistry);
            if (null !== $contactId) {
                $contactIds[] = $contactId;
            }
        }

        if ([] === $contactIds) {
            return null;
        }

        $matches = $dealRepository->findMatchingDealsByContacts($contactIds, $this->resolveCompanyId($payload, $managerRegistry));

        if (count($matches) > 1) {
            return $matches;
        }

        return $matches[0] ?? null;
    }

    /**
     * @param array<string, mixed> $payload
     */
    private function resolveCompanyId(array $payload, ManagerRegistry $managerRegistry): ?int
    {
        $company = $payload['company'] ?? null;
        if (is_array($company)) {
            if (!empty($company['id'])) {
                return (int) $company['id'];
            }

            if (!empty($company['name'])) {
                $matched = $managerRegistry->getRepository(Company::class)->findOneBy(['name' => (string) $company['name']]);

                return $matched?->getId();
            }
        }

        if (!empty($payload['company_id'])) {
            return (int) $payload['company_id'];
        }

        return null;
    }

    /**
     * @param array<string, mixed> $contact
     */
    private function resolveContactId(array $contact, ManagerRegistry $managerRegistry): ?int
    {
        if (!empty($contact['id'])) {
            return (int) $contact['id'];
        }

        if (!empty($contact['contact_id'])) {
            return (int) $contact['contact_id'];
        }

        if (!empty($contact['email'])) {
            $matched = $managerRegistry->getRepository(Lead::class)->findOneBy(['email' => (string) $contact['email']]);

            return $matched?->getId();
        }

        return null;
    }

    /**
     * @return array{source: InboundWebhookSource|null, signatureFormat: string}|null
     */
    private function resolveAuthorizedWebhookSource(
        Request $request,
        ?string $sourceKey,
        InboundWebhookSourceRepository $inboundWebhookSourceRepository,
        string $legacySecret,
    ): ?array {
        if (null !== $sourceKey && '' !== $sourceKey) {
            $source = $inboundWebhookSourceRepository->findActiveBySourceKey($sourceKey);
            if (!$source instanceof InboundWebhookSource) {
                return null;
            }

            $signatureFormat = $this->getMatchingSignatureFormat($request, $source->getSecret());

            return null === $signatureFormat ? null : [
                'source' => $source,
                'signatureFormat' => $signatureFormat,
            ];
        }

        $signatureFormat = $this->getMatchingSignatureFormat($request, $legacySecret);
        if (null === $signatureFormat) {
            return null;
        }

        return [
            'source' => null,
            'signatureFormat' => $signatureFormat,
        ];
    }

    private function getMatchingSignatureFormat(Request $request, string $secret): ?string
    {
        $secret = trim($secret);
        if ('' === $secret) {
            return null;
        }

        $nativeSignature = trim((string) $request->headers->get('Webhook-Signature', ''));
        if ('' !== $nativeSignature) {
            $expected = base64_encode(hash_hmac('sha256', $request->getContent(), $secret, true));
            if (hash_equals($expected, $nativeSignature)) {
                return 'mautic_webhook_signature';
            }
        }

        $signature = (string) $request->headers->get('X-DealFlow-Signature', '');
        $timestamp = (string) $request->headers->get('X-DealFlow-Timestamp', '');
        if ('' === $signature || '' === $timestamp || !ctype_digit($timestamp)) {
            return null;
        }

        if (abs(time() - (int) $timestamp) > 300) {
            return null;
        }

        $expected = hash_hmac('sha256', $timestamp.'.'.$request->getContent(), $secret);

        return hash_equals($expected, $signature) ? 'dealflow_timestamp_signature' : null;
    }
}
