<?php

namespace App\Modules\Payroll\Services;

use App\Modules\Admin\ActivityLog\DTOs\ActivityLogWriteData;
use App\Modules\Admin\ActivityLog\Services\ActivityLogService;
use App\Modules\Payroll\Exceptions\PayrollPeriodLockedException;
use App\Modules\Payroll\Models\PayrollPeriod;
use App\Modules\Payroll\Repositories\PayrollPeriodRepository;
use App\Modules\Payroll\Support\PayrollProtectionAuditAction;
use App\Modules\Payroll\Support\PayrollPeriodStatus;
use Carbon\Carbon;

final class PayrollProtectionService
{
    public function __construct(
        private readonly PayrollPeriodRepository $periods,
        private readonly ActivityLogService $activityLog,
    ) {}

    public function isDateLocked(int $tenantId, string $date): bool
    {
        $d = Carbon::parse($date)->toDateString();

        return $this->isRangeLocked($tenantId, $d, $d);
    }

    public function isRangeLocked(int $tenantId, string $startDate, string $endDate): bool
    {
        [$from, $to] = $this->normalizeRange($startDate, $endDate);

        return $this->periods->tenantHasFinalizedPayrollOverlappingRange($tenantId, $from, $to);
    }

    public function ensureDateWritable(
        int $tenantId,
        string $date,
        ?int $actorUserId = null,
        PayrollProtectionAuditAction $audit = PayrollProtectionAuditAction::AttendanceModification,
    ): void {
        $d = Carbon::parse($date)->toDateString();
        $this->ensureRangeWritable($tenantId, $d, $d, $actorUserId, $audit);
    }

    public function ensureRangeWritable(
        int $tenantId,
        string $startDate,
        string $endDate,
        ?int $actorUserId = null,
        PayrollProtectionAuditAction $audit = PayrollProtectionAuditAction::AttendanceModification,
    ): void {
        [$from, $to] = $this->normalizeRange($startDate, $endDate);
        if (! $this->isRangeLocked($tenantId, $from, $to)) {
            return;
        }
        $this->logBlocked($tenantId, $actorUserId, $audit, $from, $to);
        throw new PayrollPeriodLockedException($tenantId);
    }

    public function assertPayrollEditable(PayrollPeriod $period, ?int $actorUserId = null): void
    {
        if ($period->status !== PayrollPeriodStatus::FINALIZED) {
            return;
        }
        $tid = (int) $period->tenant_id;
        $this->logBlocked(
            $tid,
            $actorUserId,
            PayrollProtectionAuditAction::PayrollReprocess,
            $period->start_date->toDateString(),
            $period->end_date->toDateString(),
        );
        throw new PayrollPeriodLockedException($tid);
    }

    public function assertPayrollReprocessAllowed(PayrollPeriod $period, ?int $actorUserId = null): void
    {
        if ($period->status === PayrollPeriodStatus::DRAFT) {
            return;
        }
        $tid = (int) $period->tenant_id;
        $this->logBlocked(
            $tid,
            $actorUserId,
            PayrollProtectionAuditAction::PayrollReprocess,
            $period->start_date->toDateString(),
            $period->end_date->toDateString(),
        );
        throw new PayrollPeriodLockedException(
            $tid,
            __('payroll.protection.reprocess_locked'),
        );
    }

    public function assertSnapshotRegenerationAllowed(PayrollPeriod $period): void
    {
        if ($period->status !== PayrollPeriodStatus::FINALIZED) {
            return;
        }
        $tid = (int) $period->tenant_id;
        $this->logBlocked(
            $tid,
            null,
            PayrollProtectionAuditAction::PayrollReprocess,
            $period->start_date->toDateString(),
            $period->end_date->toDateString(),
        );
        throw new PayrollPeriodLockedException(
            $tid,
            __('payroll.protection.snapshot_finalized_locked'),
            errorCode: 'payroll_snapshot_finalized',
        );
    }

    /**
     * @return array{0: string, 1: string}
     */
    private function normalizeRange(string $startDate, string $endDate): array
    {
        $a = Carbon::parse($startDate)->toDateString();
        $b = Carbon::parse($endDate)->toDateString();

        return $a <= $b ? [$a, $b] : [$b, $a];
    }

    private function logBlocked(
        int $tenantId,
        ?int $actorUserId,
        PayrollProtectionAuditAction $audit,
        string $from,
        string $to,
    ): void {
        if ($tenantId < 1) {
            return;
        }
        $key = match ($audit) {
            PayrollProtectionAuditAction::AttendanceModification => 'attendance',
            PayrollProtectionAuditAction::LeaveSync => 'leave_sync',
            PayrollProtectionAuditAction::PayrollReprocess => 'payroll_reprocess',
        };
        $this->activityLog->log(new ActivityLogWriteData(
            tenantId: $tenantId,
            userId: $actorUserId,
            module: 'payroll',
            action: $audit->value,
            entityType: PayrollPeriod::class,
            entityId: null,
            description: __('payroll.activity.blocked.'.$key, ['from' => $from, 'to' => $to]),
        ));
    }
}
