<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Mautic\CoreBundle\Doctrine\Mapping\ClassMetadataBuilder;
use Mautic\CoreBundle\Entity\FormEntity;

class Deal extends FormEntity
{
    public const TABLE_NAME = 'dealflow_deals';

    private ?int $id = null;

    private ?Pipeline $pipeline = null;

    private ?DealStage $stage = null;

    private ?int $companyId = null;

    private ?int $ownerId = null;

    private string $title = '';

    private ?string $amount = null;

    private string $currency = 'USD';

    private ?int $confidence = null;

    private ?string $source = null;

    private ?\DateTimeInterface $expectedCloseDate = null;

    private string $status = 'open';

    private string $qualification = 'unqualified';

    private ?string $externalRef = null;

    private ?string $description = null;

    /**
     * @var array<string, mixed>|null
     */
    private ?array $customFields = [];

    /**
     * @var Collection<int, DealContactAssociation>
     */
    private Collection $contactAssociations;

    /**
     * @var Collection<int, DealNote>
     */
    private Collection $notes;

    /**
     * @var Collection<int, DealTask>
     */
    private Collection $tasks;

    /**
     * @var Collection<int, DealAttachment>
     */
    private Collection $attachments;

    /**
     * @var Collection<int, DealAuditEvent>
     */
    private Collection $auditEvents;

    public function __construct()
    {
        $this->contactAssociations = new ArrayCollection();
        $this->notes = new ArrayCollection();
        $this->tasks = new ArrayCollection();
        $this->attachments = new ArrayCollection();
        $this->auditEvents = new ArrayCollection();
    }

    /** @param ORM\ClassMetadata<static> $metadata */
    public static function loadMetadata(ORM\ClassMetadata $metadata): void
    {
        $builder = new ClassMetadataBuilder($metadata);

        $builder->setTable(self::TABLE_NAME)
            ->setCustomRepositoryClass(DealRepository::class)
            ->addIndex(['status'], 'dealflow_deal_status_idx')
            ->addIndex(['owner_id'], 'dealflow_deal_owner_idx')
            ->addIndex(['company_id'], 'dealflow_deal_company_idx')
            ->addIndex(['external_ref'], 'dealflow_deal_external_ref_idx');

        $builder->addId();

        $builder->createManyToOne('pipeline', Pipeline::class)
            ->addJoinColumn('pipeline_id', 'id', false, false, 'CASCADE')
            ->build();

        $builder->createManyToOne('stage', DealStage::class)
            ->inversedBy('deals')
            ->addJoinColumn('stage_id', 'id', false, false, 'CASCADE')
            ->build();

        $builder->createField('companyId', Types::INTEGER)
            ->columnName('company_id')
            ->nullable()
            ->build();

        $builder->createField('ownerId', Types::INTEGER)
            ->columnName('owner_id')
            ->nullable()
            ->build();

        $builder->createField('title', Types::STRING)
            ->length(191)
            ->build();

        $builder->createField('amount', Types::DECIMAL)
            ->precision(19)
            ->scale(4)
            ->nullable()
            ->build();

        $builder->createField('currency', Types::STRING)
            ->length(3)
            ->build();

        $builder->createField('confidence', Types::INTEGER)
            ->nullable()
            ->build();

        $builder->createField('source', Types::STRING)
            ->length(191)
            ->nullable()
            ->build();

        $builder->createField('expectedCloseDate', Types::DATE_MUTABLE)
            ->columnName('expected_close_date')
            ->nullable()
            ->build();

        $builder->createField('status', Types::STRING)
            ->length(32)
            ->build();

        $builder->createField('qualification', Types::STRING)
            ->length(20)
            ->build();

        $builder->createField('externalRef', Types::STRING)
            ->columnName('external_ref')
            ->length(191)
            ->nullable()
            ->build();

        $builder->createField('description', Types::TEXT)
            ->nullable()
            ->build();

        $builder->createField('customFields', Types::JSON)
            ->columnName('custom_fields_json')
            ->nullable()
            ->build();

        $builder->createOneToMany('contactAssociations', DealContactAssociation::class)
            ->mappedBy('deal')
            ->cascadePersist()
            ->cascadeRemove()
            ->fetchExtraLazy()
            ->build();

        $builder->createOneToMany('notes', DealNote::class)
            ->mappedBy('deal')
            ->cascadePersist()
            ->cascadeRemove()
            ->fetchExtraLazy()
            ->build();

        $builder->createOneToMany('tasks', DealTask::class)
            ->mappedBy('deal')
            ->cascadePersist()
            ->cascadeRemove()
            ->fetchExtraLazy()
            ->build();

        $builder->createOneToMany('attachments', DealAttachment::class)
            ->mappedBy('deal')
            ->cascadePersist()
            ->cascadeRemove()
            ->fetchExtraLazy()
            ->build();

        $builder->createOneToMany('auditEvents', DealAuditEvent::class)
            ->mappedBy('deal')
            ->cascadePersist()
            ->cascadeRemove()
            ->fetchExtraLazy()
            ->build();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getPipeline(): ?Pipeline
    {
        return $this->pipeline;
    }

    public function setPipeline(Pipeline $pipeline): self
    {
        $this->pipeline = $pipeline;

        return $this;
    }

    public function getStage(): ?DealStage
    {
        return $this->stage;
    }

    public function setStage(DealStage $stage): self
    {
        $this->stage = $stage;

        return $this;
    }

    public function getCompanyId(): ?int
    {
        return $this->companyId;
    }

    public function setCompanyId(int|string|null $companyId): self
    {
        $companyId = $this->normalizeNullableInt($companyId);
        $this->isChanged('companyId', $companyId);
        $this->companyId = $companyId;

        return $this;
    }

    public function getOwnerId(): ?int
    {
        return $this->ownerId;
    }

    public function setOwnerId(int|string|null $ownerId): self
    {
        $ownerId = $this->normalizeNullableInt($ownerId);
        $this->isChanged('ownerId', $ownerId);
        $this->ownerId = $ownerId;

        return $this;
    }

    public function getTitle(): string
    {
        return $this->title;
    }

    public function setTitle(string $title): self
    {
        $this->isChanged('title', $title);
        $this->title = $title;

        return $this;
    }

    public function getAmount(): ?string
    {
        return $this->amount;
    }

    public function setAmount(?string $amount): self
    {
        $this->isChanged('amount', $amount);
        $this->amount = $amount;

        return $this;
    }

    public function getCurrency(): string
    {
        return $this->currency;
    }

    public function setCurrency(string $currency): self
    {
        $currency = strtoupper($currency);
        $this->isChanged('currency', $currency);
        $this->currency = $currency;

        return $this;
    }

    public function getConfidence(): ?int
    {
        return $this->confidence;
    }

    public function setConfidence(int|string|null $confidence): self
    {
        $confidence = $this->normalizeNullableInt($confidence);
        $this->isChanged('confidence', $confidence);
        $this->confidence = $confidence;

        return $this;
    }

    public function getSource(): ?string
    {
        return $this->source;
    }

    public function setSource(?string $source): self
    {
        $this->isChanged('source', $source);
        $this->source = $source;

        return $this;
    }

    public function getExpectedCloseDate(): ?\DateTimeInterface
    {
        return $this->expectedCloseDate;
    }

    public function setExpectedCloseDate(?\DateTimeInterface $expectedCloseDate): self
    {
        $this->expectedCloseDate = $expectedCloseDate;

        return $this;
    }

    public function getStatus(): string
    {
        return $this->status;
    }

    public function setStatus(string $status): self
    {
        $this->isChanged('status', $status);
        $this->status = $status;

        return $this;
    }

    public function getQualification(): string
    {
        return $this->qualification;
    }

    public function setQualification(string $qualification): self
    {
        $this->isChanged('qualification', $qualification);
        $this->qualification = $qualification;

        return $this;
    }

    public function getExternalRef(): ?string
    {
        return $this->externalRef;
    }

    public function setExternalRef(?string $externalRef): self
    {
        $this->isChanged('externalRef', $externalRef);
        $this->externalRef = $externalRef;

        return $this;
    }

    public function getDescription(): ?string
    {
        return $this->description;
    }

    public function setDescription(?string $description): self
    {
        $this->isChanged('description', $description);
        $this->description = $description;

        return $this;
    }

    /**
     * @return array<string, mixed>
     */
    public function getCustomFields(): array
    {
        return $this->customFields ?? [];
    }

    /**
     * @param array<string, mixed> $customFields
     */
    public function setCustomFields(array $customFields): self
    {
        $this->isChanged('customFields', $customFields);
        $this->customFields = $customFields;

        return $this;
    }

    /**
     * @return Collection<int, DealContactAssociation>
     */
    public function getContactAssociations(): Collection
    {
        return $this->contactAssociations;
    }

    public function addContactAssociation(DealContactAssociation $association): self
    {
        if (!$this->contactAssociations->contains($association)) {
            $this->contactAssociations->add($association);
        }

        if ($association->getDeal() !== $this) {
            $association->setDeal($this);
        }

        return $this;
    }

    public function removeContactAssociation(DealContactAssociation $association): self
    {
        $this->contactAssociations->removeElement($association);

        return $this;
    }

    /**
     * @return Collection<int, DealNote>
     */
    public function getNotes(): Collection
    {
        return $this->notes;
    }

    /**
     * @return Collection<int, DealTask>
     */
    public function getTasks(): Collection
    {
        return $this->tasks;
    }

    /**
     * @return Collection<int, DealAttachment>
     */
    public function getAttachments(): Collection
    {
        return $this->attachments;
    }

    /**
     * @return Collection<int, DealAuditEvent>
     */
    public function getAuditEvents(): Collection
    {
        return $this->auditEvents;
    }

    private function normalizeNullableInt(int|string|null $value): ?int
    {
        if (null === $value) {
            return null;
        }

        if (is_string($value)) {
            $value = trim($value);
            if ('' === $value || !is_numeric($value)) {
                return null;
            }
        }

        return (int) $value;
    }
}
