<?php

namespace WordpressModels\ORM\WooCommerceEntity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Attribute\MaxDepth;
use WordpressModels\ORM\Entity\User;


/**
 * @changelog 0.7.0 - Added WooCommerce HPOS Order Entity
 */
#[ORM\Entity(readOnly: true)]
#[ORM\Table(name: "orders")]
#[ORM\Index(name: "billing_email", columns: ["billing_email"])]
#[ORM\Index(name: "customer_id_billing_email", columns: ["customer_id", "billing_email"])]
#[ORM\Index(name: "date_created", columns: ["date_created_gmt"])]
#[ORM\Index(name: "date_updated", columns: ["date_updated_gmt"])]
#[ORM\Index(name: "parent_order_id", columns: ["parent_order_id"])]
#[ORM\Index(name: "status", columns: ["status"])]
#[ORM\Index(name: "type_status_date", columns: ["type", "status", "date_created_gmt"])]
class Order
{
    #[
        ORM\Id,
        ORM\GeneratedValue(strategy: 'IDENTITY'),
        ORM\Column(type: Types::BIGINT, options: ["unsigned" => true])
    ]
    #[
        Groups(['read_order']),
        MaxDepth(1)
    ]
    private ?int $id;

    #[ORM\ManyToOne(targetEntity: self::class, inversedBy: "children")]
    #[
        Groups(['read_order']),
        MaxDepth(1)
    ]
    private ?self $parentOrder;

    #[ORM\OneToMany(mappedBy: "parentOrder", targetEntity: self::class)]
    #[
        Groups(['read_order']),
        MaxDepth(1)
    ]
    private Collection $children;

    #[ORM\OneToMany(mappedBy: 'order', targetEntity: OrderMeta::class)]
    #[
        Groups(['read_order']),
        MaxDepth(1),
        Context(normalizationContext: [
            'groups' => ['read_meta']
        ])
    ]
    private Collection $meta;

    #[ORM\Column(type: Types::STRING, length: 20, nullable: true)]
    #[Groups(['read_order'])]
    private string $status;

    #[ORM\Column(type: Types::STRING, length: 10, nullable: true)]
    #[Groups(['read_order'])]
    private string $currency;

    #[ORM\Column(type: Types::STRING, length: 20, nullable: true)]
    #[Groups(['read_order'])]
    private string $type;

    #[ORM\Column(type: Types::DECIMAL, precision: 26, scale: 8, nullable: true)]
    #[Groups(['read_order'])]
    private float $taxAmount;

    #[ORM\Column(type: Types::DECIMAL, precision: 26, scale: 8, nullable: true)]
    #[Groups(['read_order'])]
    private float $totalAmount;

    /**
     * Order customer
     * note: This foreign key is not enforced in the database, but as you should never update orders via
     * Doctrine entities, we can still define this relationship as a Doctrine M2O.
     */
    #[ORM\ManyToOne(targetEntity: User::class)]
    #[ORM\JoinColumn(name: 'customer_id', referencedColumnName: 'ID', nullable: false, options: ['default' => 0])]
    #[Groups(['read_order'])]
    #[Context(
        normalizationContext: [
            'groups' => ['read_user_simple']
        ]
    )]
    private ?User $customer;

    #[ORM\Column(type: Types::STRING, length: 320, nullable: true)]
    #[Groups(['read_order'])]
    private string $billingEmail;

    /**
     * Formatted as YYYY-MM-DD HH:MM:SS
     * @var \DateTimeInterface
     */
    #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true, options: ["default" => "0000-00-00 00:00:00"], name: "date_created_gmt")]
    #[Groups(['read_order'])]
    private \DateTimeInterface $dateCreated;

    #[ORM\Column(type: Types::DATETIME_IMMUTABLE, nullable: true, options: ["default" => "0000-00-00 00:00:00"], name: "date_updated_gmt")]
    #[Groups(['read_order'])]
    private \DateTimeInterface $dateUpdated;

    #[ORM\Column(type: Types::STRING, length: 100, nullable: true)]
    #[Groups(['read_order'])]
    private string $paymentMethod;

    #[ORM\Column(type: Types::TEXT, nullable: true)]
    #[Groups(['read_order'])]
    private string $paymentMethodTitle;

    #[ORM\Column(type: Types::STRING, length: 100, nullable: true)]
    #[Groups(['read_order'])]
    private string $transactionId;

    #[ORM\Column(type: Types::STRING, length: 100, nullable: true)]
    #[Groups(['read_order'])]
    private string $ipAddress;

    #[ORM\Column(type: Types::TEXT, nullable: true)]
    #[Groups(['read_order'])]
    private string $userAgent;

    #[ORM\Column(type: Types::TEXT, nullable: true)]
    #[Groups(['read_order'])]
    private string $customerNote;

    /**
     * Order Items
     */
    #[ORM\OneToMany(mappedBy: 'order', targetEntity: OrderItem::class)]
    #[ORM\JoinColumn(name: 'order_id', referencedColumnName: 'id')]
    #[Groups(['read_order'])]
    #[Context(
        normalizationContext: [
            'groups' => ['read_order_item']
        ]
    )]
    private Collection $items;

    public function __construct()
    {
        // init collections
        $this->children = new ArrayCollection();
        $this->meta = new ArrayCollection();
        $this->items = new ArrayCollection();
    }


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

    public function getParentOrder(): ?self
    {
        return $this->parentOrder;
    }

    public function getChildren(): Collection
    {
        return $this->children;
    }

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

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

    public function getType(): string
    {
        return $this->type;
    }

    public function getTaxAmount(): float
    {
        return $this->taxAmount;
    }

    public function getTotalAmount(): float
    {
        return $this->totalAmount;
    }

    /**
     * @return User|null
     */
    public function getCustomer(): ?User
    {
        return $this->customer;
    }

    public function getBillingEmail(): string
    {
        return $this->billingEmail;
    }

    public function getDateCreated(): \DateTimeInterface
    {
        return $this->dateCreated;
    }

    public function getDateUpdated(): \DateTimeInterface
    {
        return $this->dateUpdated;
    }

    public function getPaymentMethod(): string
    {
        return $this->paymentMethod;
    }

    public function getPaymentMethodTitle(): string
    {
        return $this->paymentMethodTitle;
    }

    public function getTransactionId(): string
    {
        return $this->transactionId;
    }

    public function getIpAddress(): string
    {
        return $this->ipAddress;
    }

    public function getUserAgent(): string
    {
        return $this->userAgent;
    }

    public function getCustomerNote(): string
    {
        return $this->customerNote;
    }

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

    public function setParentOrder(?self $parentOrder): self
    {
        $this->parentOrder = $parentOrder;
        return $this;
    }

    public function setChildren(Collection $children): self
    {
        $this->children = $children;
        return $this;
    }

    public function setStatus(string $status): self
    {
        $this->status = $status;
        return $this;
    }

    public function setCurrency(string $currency): self
    {
        $this->currency = $currency;
        return $this;
    }

    public function setType(string $type): self
    {
        $this->type = $type;
        return $this;
    }

    public function setTaxAmount(float $taxAmount): self
    {
        $this->taxAmount = $taxAmount;
        return $this;
    }

    public function setTotalAmount(float $totalAmount): self
    {
        $this->totalAmount = $totalAmount;
        return $this;
    }

    /**
     * @param User|null $customer
     */
    public function setCustomer(?User $customer): void
    {
        $this->customer = $customer;
    }

    public function setBillingEmail(string $billingEmail): self
    {
        $this->billingEmail = $billingEmail;
        return $this;
    }

    public function setDateCreated(\DateTimeInterface $dateCreated): self
    {
        $this->dateCreated = $dateCreated;
        return $this;
    }

    public function setDateUpdated(\DateTimeInterface $dateUpdated): self
    {
        $this->dateUpdated = $dateUpdated;
        return $this;
    }

    public function setPaymentMethod(string $paymentMethod): self
    {
        $this->paymentMethod = $paymentMethod;
        return $this;
    }

    public function setPaymentMethodTitle(string $paymentMethodTitle): self
    {
        $this->paymentMethodTitle = $paymentMethodTitle;
        return $this;
    }

    public function setTransactionId(string $transactionId): self
    {
        $this->transactionId = $transactionId;
        return $this;
    }

    public function setIpAddress(string $ipAddress): self
    {
        $this->ipAddress = $ipAddress;
        return $this;
    }

    public function setUserAgent(string $userAgent): self
    {
        $this->userAgent = $userAgent;
        return $this;
    }

    public function setCustomerNote(string $customerNote): self
    {
        $this->customerNote = $customerNote;
        return $this;
    }

    /**
     * @return Collection
     */
    public function getMeta(): Collection
    {
        return $this->meta;
    }

    /**
     * @param Collection $meta
     */
    public function setMeta(Collection $meta): self
    {
        $this->meta = $meta;

        return $this;
    }

    // add/remove meta
    public function addMeta(OrderMeta $meta): self
    {
        if (!$this->meta->contains($meta)) {
            $this->meta->add($meta);
        }
        return $this;
    }

    public function removeMeta(OrderMeta $meta): self
    {
        if ($this->meta->contains($meta)) {
            $this->meta->removeElement($meta);
        }
        return $this;
    }

    public function getItems(): Collection
    {
        return $this->items;
    }

    public function setItems(Collection $items): self
    {
        $this->items = $items;

        return $this;
    }

    public function addItem(OrderItem $item): self
    {
        if (!$this->items->contains($item)) {
            $this->items->add($item);
        }
        return $this;
    }

    public function removeItem(OrderItem $item): self
    {
        if ($this->items->contains($item)) {
            $this->items->removeElement($item);
        }
        return $this;
    }

    public function getMetaValue(string $key, bool $single = true): mixed
    {
        if ($single) {
            $value = $this->meta->findFirst(fn($_, OrderMeta $meta) => $meta->getKey() === $key);
            return $value->getValueDeserialized();
        }

        return $this->meta->filter(fn(OrderMeta $meta) => $meta->getKey() === $key)
            ->map(fn(OrderMeta $meta) => $meta->getValueDeserialized())
            ->toArray();
    }

    public function asWcOrder(): \WC_Abstract_Order
    {
        return wc_get_order($this->getId());
    }

}