<?php
namespace App\Services;

use App\Models\CommissionPayoutCycle;
use App\Repositories\CommissionPayoutCycleRepository;
use App\Repositories\PaymentEventRepository;
use App\Repositories\UserRepository;

class PayrollIntegrationService
{
    private $cycleRepo;
    private $userRepo;
    private $paymentEventRepo;

    public function __construct(
        CommissionPayoutCycleRepository $cycleRepo,
        UserRepository $userRepo,
        ?PaymentEventRepository $paymentEventRepo = null
    ) {
        $this->cycleRepo = $cycleRepo;
        $this->userRepo = $userRepo;
        $this->paymentEventRepo = $paymentEventRepo;
    }

    public function aggregateMonthlySettledCommissions(string $billingMonth, int $managerId = 0, int $payrollDay = 30): array
    {
        $billingMonth = trim($billingMonth);
        if (!preg_match('/^\d{4}\-\d{2}$/', $billingMonth)) {
            throw new \InvalidArgumentException('فرمت ماه نامعتبر است');
        }

        $rows = $this->cycleRepo->aggregateSettledByMonth($billingMonth);
        $results = [];
        foreach ($rows as $row) {
            $userId = (int)($row['user_id'] ?? 0);
            if ($userId <= 0) {
                continue;
            }
            $calculated = round((float)($row['calculated_amount'] ?? 0), 2);
            $lineIds = $this->parseLineIds((string)($row['commission_line_ids'] ?? ''));
            if (empty($lineIds)) {
                continue;
            }
            $approvalDeadline = $this->buildApprovalDeadline($billingMonth, $payrollDay);
            $cycle = $this->cycleRepo->findByUserAndMonth($userId, $billingMonth);
            $existingStatus = strtoupper(trim((string)($cycle->status ?? '')));

            if ($cycle && in_array($existingStatus, ['APPROVED', 'OVERRIDDEN', 'REJECTED', 'FINANCE_APPROVED', 'READY_FOR_PAYOUT', 'PAID', 'SETTLED', 'TRANSFERRED_TO_PAYROLL'], true)) {
                $results[] = $cycle->toArray();
                continue;
            }

            $entity = new CommissionPayoutCycle([
                'id' => $cycle ? (int)$cycle->id : null,
                'user_id' => $userId,
                'billing_month' => $billingMonth,
                'calculated_amount' => $calculated,
                'override_amount' => $cycle ? $cycle->override_amount : null,
                'final_payable_amount' => $cycle ? (float)$cycle->final_payable_amount : $calculated,
                'manager_id' => $managerId > 0 ? $managerId : ($cycle ? (int)$cycle->manager_id : null),
                'status' => 'PENDING_APPROVAL',
                'override_reason' => $cycle ? $cycle->override_reason : null,
                'approval_deadline' => $approvalDeadline,
                'escalate_to_finance' => 0,
            ]);
            $id = $this->cycleRepo->save($entity);
            $this->cycleRepo->attachLinesToCycle($id, $lineIds);
            $saved = $this->cycleRepo->findById($id);
            if ($saved) {
                $results[] = $saved->toArray();
            }
        }
        return $results;
    }

    public function processDeadlineSla(string $billingMonth = ''): array
    {
        $queue = $this->cycleRepo->getManagerQueue($billingMonth, 'PENDING_APPROVAL');
        $now = date('Y-m-d H:i:s');
        $updated = [];
        foreach ($queue as $cycle) {
            $deadline = trim((string)($cycle->approval_deadline ?? ''));
            if ($deadline === '' || $deadline >= $now) {
                continue;
            }
            $cycle->status = 'TRANSFERRED_TO_PAYROLL';
            $cycle->final_payable_amount = (float)$cycle->calculated_amount;
            $cycle->override_amount = null;
            $cycle->override_reason = 'SLA deadline missed -> escalated to finance';
            $cycle->escalate_to_finance = 1;
            $this->cycleRepo->save($cycle);
            $this->cycleRepo->updateCycleLinesStatus((int)$cycle->id, 'READY_FOR_PAYOUT', ['CALCULATED']);
            $this->recordCycleLineEvents((int)$cycle->id, 'COMMISSION_PAYOUT_SLA_TRANSFERRED', 0, [
                'cycle_status' => $cycle->status,
                'line_status' => 'READY_FOR_PAYOUT',
                'reason' => $cycle->override_reason,
            ]);
            $updated[] = $cycle->toArray();
        }
        return $updated;
    }

    public function getManagerQueue(string $billingMonth = '', bool $applySla = true): array
    {
        if ($applySla) {
            $this->processDeadlineSla($billingMonth);
        }
        return array_map(static function ($cycle) {
            return $cycle->toArray();
        }, $this->cycleRepo->getManagerQueue($billingMonth, 'PENDING_APPROVAL'));
    }

    public function getManagerQueueAdvanced(array $filters = [], bool $applySla = true, bool $includeLines = false): array
    {
        if ($applySla && !empty($filters['billing_month'])) {
            $this->processDeadlineSla((string)$filters['billing_month']);
        }
        return array_map(function ($cycle) use ($includeLines) {
            $row = $cycle->toArray();
            if ($includeLines) {
                $row['lines'] = $this->formatCycleLines((int)($row['id'] ?? 0));
            }
            return $row;
        }, $this->cycleRepo->getQueueAdvanced($filters));
    }

    public function decidePayoutCycle(int $cycleId, string $action, int $managerId, ?float $overrideAmount = null, string $reason = ''): CommissionPayoutCycle
    {
        if ($cycleId <= 0 || $managerId <= 0) {
            throw new \InvalidArgumentException('شناسه چرخه یا مدیر نامعتبر است');
        }

        $action = strtoupper(trim($action));
        if (!in_array($action, ['APPROVE', 'OVERRIDE', 'REJECT'], true)) {
            throw new \InvalidArgumentException('اقدام نامعتبر است');
        }

        $pdo = $this->cycleRepo->getDb()->getPdo();
        $pdo->beginTransaction();
        try {
            $cycle = $this->cycleRepo->findById($cycleId);
            if (!$cycle) {
                throw new \RuntimeException('چرخه پورسانت یافت نشد');
            }
            if (strtoupper(trim((string)$cycle->status)) !== 'PENDING_APPROVAL') {
                throw new \RuntimeException('این چرخه دیگر قابل تصمیم‌گیری مدیریتی نیست');
            }
            $this->assertCycleLineStatuses($cycleId, ['CALCULATED']);

            $cycle->manager_id = $managerId;

            if ($action === 'APPROVE') {
                $cycle->status = 'APPROVED';
                $cycle->override_amount = null;
                $cycle->final_payable_amount = (float)$cycle->calculated_amount;
                $cycle->override_reason = '';
                $cycle->escalate_to_finance = 0;
                $this->cycleRepo->updateCycleLinesStatus($cycleId, 'MANAGER_APPROVED', ['CALCULATED']);
                $this->recordCycleLineEvents($cycleId, 'COMMISSION_PAYOUT_MANAGER_APPROVED', $managerId, [
                    'action' => $action,
                    'cycle_status' => $cycle->status,
                    'line_status' => 'MANAGER_APPROVED',
                ]);
            } elseif ($action === 'OVERRIDE') {
                if ($overrideAmount === null || $overrideAmount < 0) {
                    throw new \InvalidArgumentException('مبلغ Override نامعتبر است');
                }
                if (trim($reason) === '') {
                    throw new \InvalidArgumentException('علت Override الزامی است');
                }
                $cycle->status = 'OVERRIDDEN';
                $cycle->override_amount = round((float)$overrideAmount, 2);
                $cycle->final_payable_amount = (float)$cycle->override_amount;
                $cycle->override_reason = trim($reason);
                $cycle->escalate_to_finance = 0;
                $this->cycleRepo->updateCycleLinesStatus($cycleId, 'MANAGER_APPROVED', ['CALCULATED']);
                $this->recordCycleLineEvents($cycleId, 'COMMISSION_PAYOUT_MANAGER_OVERRIDDEN', $managerId, [
                    'action' => $action,
                    'cycle_status' => $cycle->status,
                    'line_status' => 'MANAGER_APPROVED',
                    'override_amount' => $cycle->override_amount,
                    'reason' => $cycle->override_reason,
                ]);
            } else {
                if (trim($reason) === '') {
                    throw new \InvalidArgumentException('علت رد الزامی است');
                }
                $cycle->status = 'REJECTED';
                $cycle->override_amount = null;
                $cycle->final_payable_amount = 0;
                $cycle->override_reason = trim($reason);
                $cycle->escalate_to_finance = 0;
                $this->cycleRepo->updateCycleLinesStatus($cycleId, 'VOID', ['CALCULATED']);
                $this->recordCycleLineEvents($cycleId, 'COMMISSION_PAYOUT_REJECTED', $managerId, [
                    'action' => $action,
                    'cycle_status' => $cycle->status,
                    'line_status' => 'VOID',
                    'reason' => $cycle->override_reason,
                ]);
            }

            $this->cycleRepo->save($cycle);
            $pdo->commit();
            $saved = $this->cycleRepo->findById($cycleId);
            if (!$saved) {
                throw new \RuntimeException('خطا در بازیابی چرخه ذخیره‌شده');
            }
            return $saved;
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $e;
        }
    }

    public function advancePayoutCycle(int $cycleId, string $action, int $actorUserId, string $reason = ''): CommissionPayoutCycle
    {
        if ($cycleId <= 0 || $actorUserId <= 0) {
            throw new \InvalidArgumentException('شناسه چرخه یا کاربر نامعتبر است');
        }

        $action = strtoupper(trim($action));
        $map = [
            'FINANCE_APPROVE' => [
                'from_cycle' => ['APPROVED', 'OVERRIDDEN'],
                'to_cycle' => 'FINANCE_APPROVED',
                'from_line' => ['MANAGER_APPROVED'],
                'to_line' => 'FINANCE_APPROVED',
                'event' => 'COMMISSION_PAYOUT_FINANCE_APPROVED',
            ],
            'READY_FOR_PAYOUT' => [
                'from_cycle' => ['FINANCE_APPROVED'],
                'to_cycle' => 'READY_FOR_PAYOUT',
                'from_line' => ['FINANCE_APPROVED'],
                'to_line' => 'READY_FOR_PAYOUT',
                'event' => 'COMMISSION_PAYOUT_READY_FOR_PAYOUT',
            ],
            'MARK_PAID' => [
                'from_cycle' => ['READY_FOR_PAYOUT'],
                'to_cycle' => 'PAID',
                'from_line' => ['READY_FOR_PAYOUT'],
                'to_line' => 'PAID',
                'event' => 'COMMISSION_PAYOUT_PAID',
            ],
            'SETTLE' => [
                'from_cycle' => ['PAID'],
                'to_cycle' => 'SETTLED',
                'from_line' => ['PAID'],
                'to_line' => 'SETTLED',
                'event' => 'COMMISSION_PAYOUT_SETTLED',
            ],
        ];
        if (!isset($map[$action])) {
            throw new \InvalidArgumentException('اقدام نامعتبر است');
        }

        $pdo = $this->cycleRepo->getDb()->getPdo();
        $pdo->beginTransaction();
        try {
            $cycle = $this->cycleRepo->findById($cycleId);
            if (!$cycle) {
                throw new \RuntimeException('چرخه پورسانت یافت نشد');
            }
            $rule = $map[$action];
            $currentCycleStatus = strtoupper(trim((string)$cycle->status));
            if (!in_array($currentCycleStatus, $rule['from_cycle'], true)) {
                throw new \RuntimeException('انتقال وضعیت چرخه مجاز نیست');
            }
            $this->assertCycleLineStatuses($cycleId, $rule['from_line']);

            $cycle->status = $rule['to_cycle'];
            if ($action === 'MARK_PAID' || $action === 'SETTLE') {
                $cycle->escalate_to_finance = 0;
            }
            if (trim($reason) !== '') {
                $cycle->override_reason = trim($reason);
            }
            $this->cycleRepo->updateCycleLinesStatus($cycleId, $rule['to_line'], $rule['from_line']);
            $this->recordCycleLineEvents($cycleId, $rule['event'], $actorUserId, [
                'action' => $action,
                'cycle_status' => $rule['to_cycle'],
                'line_status' => $rule['to_line'],
                'reason' => trim($reason),
            ]);
            $this->cycleRepo->save($cycle);
            $pdo->commit();

            $saved = $this->cycleRepo->findById($cycleId);
            if (!$saved) {
                throw new \RuntimeException('خطا در بازیابی چرخه ذخیره‌شده');
            }
            return $saved;
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) {
                $pdo->rollBack();
            }
            throw $e;
        }
    }

    private function buildApprovalDeadline(string $billingMonth, int $payrollDay): string
    {
        $payrollDay = max(1, min(31, $payrollDay));
        $year = (int)substr($billingMonth, 0, 4);
        $month = (int)substr($billingMonth, 5, 2);
        $daysInMonth = (int)date('t', strtotime(sprintf('%04d-%02d-01', $year, $month)));
        $effectivePayrollDay = min($payrollDay, $daysInMonth);
        $payrollDate = sprintf('%04d-%02d-%02d', $year, $month, $effectivePayrollDay);
        $deadline = date('Y-m-d', strtotime($payrollDate . ' -2 days'));
        return $deadline . ' 23:59:59';
    }

    private function parseLineIds(string $lineIds): array
    {
        if (trim($lineIds) === '') {
            return [];
        }
        return array_values(array_unique(array_filter(array_map('intval', explode(',', $lineIds)), static function ($id) {
            return $id > 0;
        })));
    }

    private function assertCycleLineStatuses(int $cycleId, array $allowedStatuses): void
    {
        $allowedStatuses = array_values(array_unique(array_map(static function ($status) {
            return strtoupper(trim((string)$status));
        }, $allowedStatuses)));
        $counts = $this->cycleRepo->getCycleLineStatusCounts($cycleId);
        if (empty($counts)) {
            throw new \RuntimeException('هیچ line پورسانتی به این چرخه وصل نیست');
        }
        foreach ($counts as $status => $count) {
            if ($count > 0 && !in_array(strtoupper((string)$status), $allowedStatuses, true)) {
                throw new \RuntimeException('وضعیت lineهای پورسانت با انتقال درخواستی سازگار نیست');
            }
        }
    }

    private function recordCycleLineEvents(int $cycleId, string $eventType, int $actorUserId, array $metadata): void
    {
        if (!$this->paymentEventRepo) {
            return;
        }
        foreach ($this->cycleRepo->getCycleLines($cycleId) as $line) {
            $paymentRecordId = (int)($line['payment_record_id'] ?? 0);
            if ($paymentRecordId <= 0) {
                continue;
            }
            $this->paymentEventRepo->recordEvent(
                $paymentRecordId,
                $eventType,
                [],
                [
                    'commission_line_id' => (int)($line['id'] ?? 0),
                    'commission_line_status' => (string)($line['status'] ?? ''),
                ],
                array_merge($metadata, [
                    'payout_cycle_id' => $cycleId,
                    'commission_line_id' => (int)($line['id'] ?? 0),
                    'beneficiary_user_id' => (int)($line['beneficiary_user_id'] ?? 0),
                    'beneficiary_type' => (string)($line['beneficiary_type'] ?? ''),
                    'calculated_commission' => (float)($line['calculated_commission'] ?? 0),
                ]),
                $actorUserId
            );
        }
    }

    private function formatCycleLines(int $cycleId): array
    {
        $rows = $this->cycleRepo->getCycleLines($cycleId);
        return array_map(function (array $row) {
            $customerName = trim((string)($row['customer_name'] ?? ''));
            $dealTitle = trim((string)($row['deal_title'] ?? ''));
            $paymentOwnerName = trim((string)($row['payment_owner_name_snapshot'] ?? ''));
            return [
                'id' => (int)($row['id'] ?? 0),
                'payment_record_id' => (int)($row['payment_record_id'] ?? 0),
                'payment_uuid' => (string)($row['payment_uuid'] ?? ''),
                'payment_sequence' => isset($row['payment_sequence']) ? (int)$row['payment_sequence'] : null,
                'payment_label' => $this->paymentLabel($row),
                'payment_method' => (string)($row['payment_method'] ?? ''),
                'payment_date' => (string)($row['payment_date'] ?? ''),
                'deposit_date' => (string)($row['deposit_date'] ?? ''),
                'payment_effective_date' => (string)($row['payment_effective_date'] ?? ''),
                'effective_date' => (string)($row['effective_date'] ?? ''),
                'payment_amount' => (float)($row['payment_amount'] ?? $row['payment_amount_snapshot'] ?? 0),
                'payment_amount_snapshot' => (float)($row['payment_amount_snapshot'] ?? 0),
                'commission_base_amount' => (float)($row['commission_base_amount'] ?? 0),
                'applied_rate_percent' => (float)($row['applied_rate_percent'] ?? 0),
                'calculated_commission' => (float)($row['calculated_commission'] ?? 0),
                'beneficiary_type' => (string)($row['beneficiary_type'] ?? ''),
                'status' => (string)($row['status'] ?? ''),
                'customer_name' => $customerName,
                'customer_code' => (string)($row['customer_code'] ?? ''),
                'deal_title' => $dealTitle,
                'deal_code' => (string)($row['deal_code'] ?? ''),
                'deal_invoice_code' => (string)($row['deal_invoice_code'] ?? ''),
                'deal_didar_id' => (string)($row['deal_didar_id'] ?? ''),
                'payment_owner_name' => $paymentOwnerName,
                'product_name' => (string)($row['product_name'] ?? ''),
                'rule_title' => (string)($row['rule_title'] ?? ''),
                'cheque_status' => (string)($row['cheque_status'] ?? ''),
                'cheque_collection_status' => (string)($row['cheque_collection_status'] ?? ''),
                'cheque_collection_date' => (string)($row['cheque_collection_date'] ?? ''),
                'source_title' => $customerName !== '' ? $customerName : ($dealTitle !== '' ? $dealTitle : (string)($row['deal_didar_id'] ?? '')),
            ];
        }, $rows);
    }

    private function paymentLabel(array $row): string
    {
        $sequence = $row['payment_sequence'] ?? null;
        if ($sequence !== null && $sequence !== '') {
            return 'پرداخت ' . (int)$sequence;
        }
        $id = (int)($row['payment_record_id'] ?? 0);
        return $id > 0 ? 'پرداخت #' . $id : 'پرداخت';
    }
}
