<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\Service;

use Doctrine\Persistence\ManagerRegistry;
use Mautic\LeadBundle\Entity\Company;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\UserBundle\Entity\User;
use MauticPlugin\DealFlowBundle\Entity\Deal;

class DealContextBuilder
{
    public function __construct(
        private readonly ManagerRegistry $managerRegistry,
    ) {
    }

    /**
     * @param list<Deal> $deals
     *
     * @return array<int, array{companyName: ?string, ownerName: ?string, contacts: list<array{id:int,label:?string,name:string,email:?string,createdAt:\DateTimeInterface}>}>
     */
    public function buildForDeals(array $deals): array
    {
        $companyIds = [];
        $ownerIds = [];
        $contactIds = [];

        foreach ($deals as $deal) {
            if (null !== $deal->getCompanyId()) {
                $companyIds[] = $deal->getCompanyId();
            }

            if (null !== $deal->getOwnerId()) {
                $ownerIds[] = $deal->getOwnerId();
            }

            foreach ($deal->getContactAssociations() as $association) {
                $contactIds[] = $association->getContactId();
            }
        }

        $companies = $this->loadCompanies($companyIds);
        $owners = $this->loadOwners($ownerIds);
        $contacts = $this->loadContacts($contactIds);

        $contexts = [];
        foreach ($deals as $deal) {
            $contactContext = [];
            foreach ($deal->getContactAssociations() as $association) {
                $contact = $contacts[$association->getContactId()] ?? [
                    'name' => 'Contact #'.$association->getContactId(),
                    'email' => null,
                ];

                $contactContext[] = [
                    'id' => $association->getContactId(),
                    'label' => $association->getLabel(),
                    'name' => $contact['name'],
                    'email' => $contact['email'],
                    'createdAt' => $association->getCreatedAt(),
                ];
            }

            $contexts[$deal->getId() ?? spl_object_id($deal)] = [
                'companyName' => null !== $deal->getCompanyId() ? ($companies[$deal->getCompanyId()] ?? 'Company #'.$deal->getCompanyId()) : null,
                'ownerName' => null !== $deal->getOwnerId() ? ($owners[$deal->getOwnerId()] ?? 'User #'.$deal->getOwnerId()) : null,
                'contacts' => $contactContext,
            ];
        }

        return $contexts;
    }

    /**
     * @return array{companyName: ?string, ownerName: ?string, contacts: list<array{id:int,label:?string,name:string,email:?string,createdAt:\DateTimeInterface}>}
     */
    public function buildForDeal(Deal $deal): array
    {
        return $this->buildForDeals([$deal])[$deal->getId() ?? spl_object_id($deal)];
    }

    /**
     * @param array<string, mixed> $extra
     *
     * @return array<string, mixed>
     */
    public function buildWebhookPayload(Deal $deal, string $eventName, string $origin, ?string $actorType, ?int $actorId, array $extra = []): array
    {
        $context = $this->buildForDeal($deal);

        return array_merge(
            [
                'event' => $eventName,
                'occurred_at' => $deal->getDateModified()->format(DATE_ATOM),
                'origin' => $origin,
                'actor' => [
                    'type' => $actorType,
                    'id' => $actorId,
                ],
                'deal' => [
                    'id' => $deal->getId(),
                    'title' => $deal->getTitle(),
                    'status' => $deal->getStatus(),
                    'stage' => $deal->getStage()?->getName(),
                    'amount' => $deal->getAmount(),
                    'currency' => $deal->getCurrency(),
                    'confidence' => $deal->getConfidence(),
                    'owner_id' => $deal->getOwnerId(),
                    'company_id' => $deal->getCompanyId(),
                    'external_ref' => $deal->getExternalRef(),
                    'expected_close_date' => $deal->getExpectedCloseDate()?->format('Y-m-d'),
                    'updated_at' => $deal->getDateModified()->format(DATE_ATOM),
                ],
                'company' => null !== $deal->getCompanyId() ? [
                    'id' => $deal->getCompanyId(),
                    'name' => $context['companyName'],
                ] : null,
                'contacts' => $context['contacts'],
            ],
            $extra
        );
    }

    /**
     * @param list<int> $companyIds
     *
     * @return array<int, string>
     */
    private function loadCompanies(array $companyIds): array
    {
        if ([] === $companyIds) {
            return [];
        }

        $companies = $this->managerRegistry->getRepository(Company::class)->findBy(['id' => array_values(array_unique($companyIds))]);
        $results = [];

        foreach ($companies as $company) {
            $results[$company->getId()] = $company->getName() ?: 'Company #'.$company->getId();
        }

        return $results;
    }

    /**
     * @param list<int> $ownerIds
     *
     * @return array<int, string>
     */
    private function loadOwners(array $ownerIds): array
    {
        if ([] === $ownerIds) {
            return [];
        }

        $owners = $this->managerRegistry->getRepository(User::class)->findBy(['id' => array_values(array_unique($ownerIds))]);
        $results = [];

        foreach ($owners as $owner) {
            $name = method_exists($owner, 'getName') ? $owner->getName() : (method_exists($owner, 'getUserIdentifier') ? $owner->getUserIdentifier() : 'User #'.$owner->getId());
            $results[$owner->getId()] = $name ?: 'User #'.$owner->getId();
        }

        return $results;
    }

    /**
     * @param list<int> $contactIds
     *
     * @return array<int, array{name:string,email:?string}>
     */
    private function loadContacts(array $contactIds): array
    {
        if ([] === $contactIds) {
            return [];
        }

        $contacts = $this->managerRegistry->getRepository(Lead::class)->findBy(['id' => array_values(array_unique($contactIds))]);
        $results = [];

        foreach ($contacts as $contact) {
            $name = trim(sprintf('%s %s', (string) $contact->getFirstname(), (string) $contact->getLastname()));
            if ('' === $name) {
                $name = $contact->getEmail() ?: 'Contact #'.$contact->getId();
            }

            $results[$contact->getId()] = [
                'name' => $name,
                'email' => $contact->getEmail(),
            ];
        }

        return $results;
    }
}
