<?php

declare(strict_types=1);

namespace App\Entity;

use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\ApiResource;
use App\Dto\Contracts\ContractPhase;
use App\Dto\Employees\ContractHistoryItem;
use App\Enum\ContractNature;
use App\Repository\EmployeeRepository;
use App\Service\Contracts\EmployeeContractPhaseResolver;
use App\State\EmployeeWriteProcessor;
use DateTimeImmutable;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Attribute\Context;
use Symfony\Component\Serializer\Attribute\Groups;
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;

#[ApiResource(
    normalizationContext: ['groups' => ['employee:read', 'site:read']],
    denormalizationContext: ['groups' => ['employee:write']],
    paginationEnabled: false,
    security: "is_granted('ROLE_ADMIN')",
    processor: EmployeeWriteProcessor::class,
    order: ['site.name' => 'ASC', 'displayOrder' => 'ASC', 'lastName' => 'ASC', 'firstName' => 'ASC'],
)]
#[ORM\Entity(repositoryClass: EmployeeRepository::class)]
#[ORM\Table(name: 'employees')]
class Employee
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    #[Groups(['absence:read', 'employee:read'])]
    private ?int $id = null;

    #[ORM\Column(type: 'string', length: 100)]
    #[Groups(['absence:read', 'employee:read', 'employee:write'])]
    private string $firstName = '';

    #[ORM\Column(type: 'string', length: 100)]
    #[Groups(['absence:read', 'employee:read', 'employee:write'])]
    private string $lastName = '';

    #[ApiProperty(readableLink: true)]
    #[ORM\ManyToOne(targetEntity: Site::class)]
    #[ORM\JoinColumn(nullable: true)]
    #[Groups(['employee:read', 'employee:write'])]
    private ?Site $site = null;

    #[ApiProperty(readableLink: true)]
    #[ORM\ManyToOne(targetEntity: Contract::class)]
    #[ORM\JoinColumn(nullable: false)]
    #[Groups(['employee:read', 'employee:write'])]
    private ?Contract $contract = null;

    #[ORM\Column(type: 'integer', options: ['default' => 0])]
    #[Groups(['employee:read', 'employee:write'])]
    private int $displayOrder = 0;

    #[ORM\Column(type: 'date_immutable', nullable: true)]
    #[Groups(['employee:read'])]
    #[Context([DateTimeNormalizer::FORMAT_KEY => 'Y-m-d'])]
    private ?DateTimeImmutable $entryDate = null;

    #[ORM\Column(type: 'datetime_immutable')]
    private DateTimeImmutable $createdAt;

    /**
     * @var Collection<int, EmployeeContractPeriod>
     */
    #[ORM\OneToMany(mappedBy: 'employee', targetEntity: EmployeeContractPeriod::class)]
    private Collection $contractPeriods;

    #[Groups(['employee:write'])]
    private ?string $contractNature = null;

    #[Groups(['employee:write'])]
    private ?string $contractStartDate = null;

    #[Groups(['employee:write'])]
    private ?string $contractEndDate = null;

    #[Groups(['employee:write'])]
    private ?bool $contractPaidLeaveSettled = null;

    #[Groups(['employee:write'])]
    private ?string $contractComment = null;

    #[Groups(['employee:write'])]
    private ?bool $isDriverInput = null;

    /**
     * @var null|array<int, int> iso-day → minutes, write-only (propagated to EmployeeContractPeriod)
     */
    #[Groups(['employee:write'])]
    private ?array $workDaysHoursInput = null;

    #[Groups(['employee:write'])]
    private ?int $interimAgencyId = null;

    public function __construct()
    {
        $this->createdAt       = new DateTimeImmutable();
        $this->contractPeriods = new ArrayCollection();
    }

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

    public function getFirstName(): string
    {
        return $this->firstName;
    }

    public function setFirstName(string $firstName): self
    {
        $this->firstName = $firstName;

        return $this;
    }

    public function getLastName(): string
    {
        return $this->lastName;
    }

    public function setLastName(string $lastName): self
    {
        $this->lastName = $lastName;

        return $this;
    }

    #[Groups(['employee:read'])]
    public function getInitials(): string
    {
        $first = mb_strtoupper(mb_substr(trim($this->firstName), 0, 1));
        $last  = mb_strtoupper(mb_substr(trim($this->lastName), 0, 1));

        return $first.$last;
    }

    public function getSite(): ?Site
    {
        return $this->site;
    }

    public function setSite(?Site $site): self
    {
        $this->site = $site;

        return $this;
    }

    public function getContract(): ?Contract
    {
        return $this->contract;
    }

    public function setContract(?Contract $contract): self
    {
        $this->contract = $contract;

        return $this;
    }

    public function getCreatedAt(): DateTimeImmutable
    {
        return $this->createdAt;
    }

    public function getDisplayOrder(): int
    {
        return $this->displayOrder;
    }

    public function setDisplayOrder(int $displayOrder): self
    {
        $this->displayOrder = $displayOrder;

        return $this;
    }

    public function getEntryDate(): ?DateTimeImmutable
    {
        return $this->entryDate;
    }

    public function setEntryDate(?DateTimeImmutable $entryDate): self
    {
        $this->entryDate = $entryDate;

        return $this;
    }

    public function getContractNature(): ?string
    {
        return $this->contractNature;
    }

    public function setContractNature(?string $contractNature): self
    {
        $this->contractNature = $contractNature;

        return $this;
    }

    public function getContractStartDate(): ?string
    {
        return $this->contractStartDate;
    }

    public function setContractStartDate(?string $contractStartDate): self
    {
        $this->contractStartDate = $contractStartDate;

        return $this;
    }

    public function getContractEndDate(): ?string
    {
        return $this->contractEndDate;
    }

    public function setContractEndDate(?string $contractEndDate): self
    {
        $this->contractEndDate = $contractEndDate;

        return $this;
    }

    public function getContractPaidLeaveSettled(): ?bool
    {
        return $this->contractPaidLeaveSettled;
    }

    public function setContractPaidLeaveSettled(?bool $contractPaidLeaveSettled): self
    {
        $this->contractPaidLeaveSettled = $contractPaidLeaveSettled;

        return $this;
    }

    public function getContractComment(): ?string
    {
        return $this->contractComment;
    }

    public function setContractComment(?string $contractComment): self
    {
        $this->contractComment = $contractComment;

        return $this;
    }

    public function getIsDriverInput(): ?bool
    {
        return $this->isDriverInput;
    }

    public function setIsDriverInput(?bool $isDriverInput): self
    {
        $this->isDriverInput = $isDriverInput;

        return $this;
    }

    /**
     * @return null|array<int, int>
     */
    public function getWorkDaysHoursInput(): ?array
    {
        return $this->workDaysHoursInput;
    }

    /**
     * @param null|array<int|string, mixed> $workDaysHoursInput
     */
    public function setWorkDaysHoursInput(?array $workDaysHoursInput): self
    {
        if (null === $workDaysHoursInput) {
            $this->workDaysHoursInput = null;

            return $this;
        }

        $normalized = [];
        foreach ($workDaysHoursInput as $key => $value) {
            $normalized[(int) $key] = (int) $value;
        }
        $this->workDaysHoursInput = $normalized;

        return $this;
    }

    public function getInterimAgencyId(): ?int
    {
        return $this->interimAgencyId;
    }

    public function setInterimAgencyId(?int $interimAgencyId): self
    {
        $this->interimAgencyId = $interimAgencyId;

        return $this;
    }

    #[Groups(['employee:read'])]
    public function getCurrentInterimAgencyId(): ?int
    {
        return $this->resolveCurrentContractPeriod()?->getInterimAgency()?->getId();
    }

    #[Groups(['employee:read'])]
    public function getCurrentInterimAgencyName(): ?string
    {
        return $this->resolveCurrentContractPeriod()?->getInterimAgency()?->getName();
    }

    #[Groups(['employee:read'])]
    public function getHasActiveContract(): bool
    {
        return null !== $this->resolveCurrentContractPeriod();
    }

    #[Groups(['employee:read'])]
    public function getIsDriver(): bool
    {
        return $this->resolveCurrentContractPeriod()?->getIsDriver() ?? false;
    }

    #[Groups(['employee:read'])]
    public function getCurrentContractNature(): string
    {
        return $this->resolveCurrentContractPeriod()?->getContractNatureEnum()->value ?? ContractNature::CDI->value;
    }

    #[Groups(['employee:read'])]
    public function getCurrentContractStartDate(): ?string
    {
        return $this->resolveCurrentContractPeriod()?->getStartDate()->format('Y-m-d');
    }

    #[Groups(['employee:read'])]
    public function getCurrentContractEndDate(): ?string
    {
        return $this->resolveCurrentContractPeriod()?->getEndDate()?->format('Y-m-d');
    }

    /**
     * @return list<array{id: null|int, startDate: string, endDate: null|string, comment: null|string}>
     */
    #[Groups(['employee:read'])]
    public function getCurrentSuspensions(): array
    {
        $currentPeriod = $this->resolveCurrentContractPeriod();
        if (null === $currentPeriod) {
            return [];
        }

        return array_values(array_map(
            static fn (ContractSuspension $s): array => [
                'id'        => $s->getId(),
                'startDate' => $s->getStartDate()->format('Y-m-d'),
                'endDate'   => $s->getEndDate()?->format('Y-m-d'),
                'comment'   => $s->getComment(),
            ],
            $currentPeriod->getSuspensions()->toArray()
        ));
    }

    /**
     * @return Collection<int, EmployeeContractPeriod>
     */
    public function getContractPeriods(): Collection
    {
        return $this->contractPeriods;
    }

    /**
     * @return list<ContractHistoryItem>
     */
    #[Groups(['employee:read'])]
    public function getContractHistory(): array
    {
        $periods = $this->contractPeriods->toArray();
        usort(
            $periods,
            static fn (EmployeeContractPeriod $a, EmployeeContractPeriod $b): int => $b->getStartDate() <=> $a->getStartDate()
        );

        return array_map(
            static function (EmployeeContractPeriod $period): ContractHistoryItem {
                $contract = $period->getContract();

                $suspensionData = array_map(
                    static fn (ContractSuspension $s): array => [
                        'id'        => $s->getId(),
                        'startDate' => $s->getStartDate()->format('Y-m-d'),
                        'endDate'   => $s->getEndDate()?->format('Y-m-d'),
                        'comment'   => $s->getComment(),
                    ],
                    $period->getSuspensions()->toArray()
                );

                return new ContractHistoryItem(
                    contractId: $contract?->getId(),
                    contractName: $contract?->getName(),
                    weeklyHours: $contract?->getWeeklyHours(),
                    contractNature: $period->getContractNatureEnum()->value,
                    startDate: $period->getStartDate()->format('Y-m-d'),
                    endDate: $period->getEndDate()?->format('Y-m-d'),
                    comment: $period->getComment(),
                    periodId: $period->getId(),
                    suspensions: $suspensionData,
                    isDriver: $period->getIsDriver(),
                    workDaysHours: $period->getWorkDaysHours(),
                    interimAgencyId: $period->getInterimAgency()?->getId(),
                    interimAgencyName: $period->getInterimAgency()?->getName(),
                );
            },
            $periods
        );
    }

    /**
     * @return list<array{
     *   id: int,
     *   contractType: string,
     *   weeklyHours: ?int,
     *   isDriver: bool,
     *   startDate: string,
     *   endDate: ?string,
     *   periodIds: list<int>,
     *   isCurrent: bool,
     *   contractNature: string
     * }>
     */
    #[Groups(['employee:read'])]
    public function getContractPhases(): array
    {
        // Read RTT_START_DATE directly here: the entity has no DI but must filter
        // out contract phases that ended before the application's data start.
        $rawDate  = $_SERVER['RTT_START_DATE'] ?? $_ENV['RTT_START_DATE'] ?? '';
        $resolver = new EmployeeContractPhaseResolver(is_string($rawDate) ? $rawDate : '');

        return array_map(
            static fn (ContractPhase $phase): array => [
                'id'             => $phase->id,
                'contractType'   => $phase->contractType->value,
                'weeklyHours'    => $phase->weeklyHours,
                'isDriver'       => $phase->isDriver,
                'startDate'      => $phase->startDate->format('Y-m-d'),
                'endDate'        => $phase->endDate?->format('Y-m-d'),
                'periodIds'      => $phase->periodIds,
                'isCurrent'      => $phase->isCurrent,
                'contractNature' => $phase->contractNature->value,
            ],
            $resolver->resolvePhases($this),
        );
    }

    private function resolveCurrentContractPeriod(): ?EmployeeContractPeriod
    {
        $today   = new DateTimeImmutable('today');
        $current = null;

        foreach ($this->contractPeriods as $period) {
            if ($period->getStartDate() > $today) {
                continue;
            }

            $endDate = $period->getEndDate();
            if (null !== $endDate && $endDate < $today) {
                continue;
            }

            if (null === $current || $period->getStartDate() > $current->getStartDate()) {
                $current = $period;
            }
        }

        return $current;
    }
}
