<?php

namespace App\Domain\HR\Services;

use App\Domain\HR\Enums\CompensationStatus;
use App\Domain\HR\Enums\CompensationType;
use App\Domain\HR\Models\Trainer;
use App\Domain\HR\Models\TrainerCompensation;
use App\Domain\Shared\Services\SettingsService;
use App\Domain\Training\Models\TrainingSession;
use App\Models\User;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;

class CompensationCalculatorService
{
    public function __construct(
        private SettingsService $settings,
    ) {}

    /**
     * Calculate compensation for a trainer attending a session.
     * Called when attendance is marked (present, late, or substitute).
     */
    public function calculateForSession(
        Trainer $trainer,
        int $trainingSessionId,
        int $attendanceRecordId,
        string $attendanceStatus, // 'present', 'late', 'substitute'
        User $actor,
    ): ?TrainerCompensation {
        if (!(bool) $this->settings->get('auto_trainer_compensation_enabled', false)) {
            return null;
        }

        // Don't double-create
        $existing = TrainerCompensation::where('trainer_id', $trainer->id)
            ->where('training_session_id', $trainingSessionId)
            ->whereNotIn('status', [CompensationStatus::Cancelled->value])
            ->first();

        if ($existing) {
            return $existing;
        }

        $rate = $this->resolveSessionRate($trainer);
        if ($rate === 0) {
            return null; // Salaried trainer with no per-session rate
        }

        $type = match ($attendanceStatus) {
            'substitute' => CompensationType::Substitute,
            default      => CompensationType::SessionPay,
        };

        $quantity = $this->resolveSessionQuantity($trainer, $trainingSessionId);
        $amount = (int) round($rate * $quantity);

        return DB::transaction(function () use ($trainer, $trainingSessionId, $attendanceRecordId, $rate, $quantity, $amount, $type, $actor) {
            return TrainerCompensation::create([
                'academy_id'           => $trainer->academy_id,
                'trainer_id'           => $trainer->id,
                'training_session_id'  => $trainingSessionId,
                'attendance_record_id' => $attendanceRecordId,
                'date'                 => now()->toDateString(),
                'type'                 => $type,
                'description'          => $type->label(),
                'quantity'             => $quantity,
                'rate'                 => $rate,
                'amount'               => $amount,
                'status'               => CompensationStatus::Pending,
                'created_by'           => $actor->id,
            ]);
        });
    }

    /**
     * Calculate penalty for a no-show (absent without excuse).
     */
    public function calculatePenalty(
        Trainer $trainer,
        int $trainingSessionId,
        int $attendanceRecordId,
        User $actor,
    ): ?TrainerCompensation {
        if (!(bool) $this->settings->get('trainer_absence_penalty_enabled', false)) {
            return null;
        }

        $penaltyAmount = (int) $this->settings->get('absence_penalty_amount', 0);
        if ($penaltyAmount <= 0) {
            return null;
        }

        $existing = TrainerCompensation::where('trainer_id', $trainer->id)
            ->where('training_session_id', $trainingSessionId)
            ->where('type', CompensationType::Penalty->value)
            ->whereNotIn('status', [CompensationStatus::Cancelled->value])
            ->first();

        if ($existing) {
            return $existing;
        }

        return DB::transaction(function () use ($trainer, $trainingSessionId, $attendanceRecordId, $penaltyAmount, $actor) {
            return TrainerCompensation::create([
                'academy_id'           => $trainer->academy_id,
                'trainer_id'           => $trainer->id,
                'training_session_id'  => $trainingSessionId,
                'attendance_record_id' => $attendanceRecordId,
                'date'                 => now()->toDateString(),
                'type'                 => CompensationType::Penalty,
                'description'          => 'خصم غياب بدون عذر',
                'quantity'             => 1,
                'rate'                 => $penaltyAmount,
                'amount'               => $penaltyAmount,
                'status'               => CompensationStatus::Pending,
                'created_by'           => $actor->id,
            ]);
        });
    }

    /**
     * Calculate late penalty (less than full absence).
     */
    public function calculateLatePenalty(
        Trainer $trainer,
        int $trainingSessionId,
        int $attendanceRecordId,
        User $actor,
    ): ?TrainerCompensation {
        if (!(bool) $this->settings->get('trainer_late_penalty_enabled', false)) {
            return null;
        }

        $latePenalty = (int) $this->settings->get('late_penalty_amount', 0);
        if ($latePenalty <= 0) {
            return null;
        }

        $existing = TrainerCompensation::where('trainer_id', $trainer->id)
            ->where('training_session_id', $trainingSessionId)
            ->where('type', CompensationType::Penalty->value)
            ->whereNotIn('status', [CompensationStatus::Cancelled->value])
            ->first();

        if ($existing) {
            return $existing;
        }

        return DB::transaction(function () use ($trainer, $trainingSessionId, $attendanceRecordId, $latePenalty, $actor) {
            return TrainerCompensation::create([
                'academy_id'           => $trainer->academy_id,
                'trainer_id'           => $trainer->id,
                'training_session_id'  => $trainingSessionId,
                'attendance_record_id' => $attendanceRecordId,
                'date'                 => now()->toDateString(),
                'type'                 => CompensationType::Penalty,
                'description'          => 'خصم تأخير',
                'quantity'             => 1,
                'rate'                 => $latePenalty,
                'amount'               => $latePenalty,
                'status'               => CompensationStatus::Pending,
                'created_by'           => $actor->id,
            ]);
        });
    }

    /**
     * Calculate compensation when academy cancels a session.
     * Trainer gets a configurable % of their rate.
     */
    public function calculateCancelledSessionPay(
        Trainer $trainer,
        int $trainingSessionId,
        User $actor,
    ): ?TrainerCompensation {
        if (!(bool) $this->settings->get('cancelled_session_pay_enabled', false)) {
            return null;
        }

        $payPercent = (int) $this->settings->get('cancelled_session_pay_percent', 0);
        if ($payPercent <= 0) {
            return null;
        }

        $rate = $this->resolveSessionRate($trainer);
        if ($rate === 0) {
            return null;
        }

        $amount = (int) round($rate * $payPercent / 100);

        return DB::transaction(function () use ($trainer, $trainingSessionId, $amount, $rate, $payPercent, $actor) {
            return TrainerCompensation::create([
                'academy_id'           => $trainer->academy_id,
                'trainer_id'           => $trainer->id,
                'training_session_id'  => $trainingSessionId,
                'attendance_record_id' => null,
                'date'                 => now()->toDateString(),
                'type'                 => CompensationType::CancelledSession,
                'description'          => "تعويض حصة ملغاة ({$payPercent}%)",
                'quantity'             => 1,
                'rate'                 => $rate,
                'amount'               => $amount,
                'status'               => CompensationStatus::Pending,
                'metadata'             => ['pay_percent' => $payPercent],
                'created_by'           => $actor->id,
            ]);
        });
    }

    /**
     * Calculate monthly per-player compensation.
     * Called during payroll period calculation.
     */
    public function calculatePlayerPay(
        Trainer $trainer,
        Carbon $periodStart,
        Carbon $periodEnd,
        User $actor,
    ): ?TrainerCompensation {
        if (!$trainer->player_rate || $trainer->player_rate <= 0) {
            return null;
        }

        if (!in_array($trainer->compensation_model->value, ['per_player', 'hybrid'])) {
            return null;
        }

        if (!(bool) $this->settings->get('per_player_compensation_enabled', false)) {
            return null;
        }

        // Resolve trainer's user_id (head_trainer_id on groups references users.id)
        $userId = $trainer->employee?->user_id;
        if (!$userId) {
            return null;
        }

        // Count active enrollments in trainer's assigned groups during this period.
        //
        // Deliberately across every branch: a trainer draws one salary for all
        // the players they coach, and this number is multiplied by player_rate
        // into real money on a payslip. Left implicit, the branch scope would
        // quietly pay them only for the branch whoever pressed Calculate
        // happened to have selected — and nothing on the payslip would say so.
        $playerCount = \App\Domain\Training\Models\TrainingGroup::acrossBranches(
            fn () => \App\Domain\Training\Models\TrainingGroup::where('head_trainer_id', $userId)
                ->where('status', 'active')
                ->withCount(['enrollments' => function ($q) {
                    $q->where('status', 'active');
                }])
                ->get()
                ->sum('enrollments_count')
        );

        if ($playerCount <= 0) {
            return null;
        }

        $amount = $playerCount * $trainer->player_rate;

        return DB::transaction(function () use ($trainer, $periodStart, $periodEnd, $playerCount, $amount, $actor) {
            return TrainerCompensation::create([
                'academy_id'           => $trainer->academy_id,
                'trainer_id'           => $trainer->id,
                'training_session_id'  => null,
                'attendance_record_id' => null,
                'date'                 => $periodEnd->toDateString(),
                'type'                 => CompensationType::PlayerPay,
                'description'          => "أجر لاعبين ({$playerCount} لاعب)",
                'quantity'             => $playerCount,
                'rate'                 => $trainer->player_rate,
                'amount'               => $amount,
                'status'               => CompensationStatus::Pending,
                'metadata'             => [
                    'period_start' => $periodStart->toDateString(),
                    'period_end'   => $periodEnd->toDateString(),
                    'player_count' => $playerCount,
                ],
                'created_by'           => $actor->id,
            ]);
        });
    }

    /**
     * Calculate monthly revenue share compensation.
     * Called during payroll period calculation.
     */
    public function calculateRevenueShare(
        Trainer $trainer,
        Carbon $periodStart,
        Carbon $periodEnd,
        User $actor,
    ): ?TrainerCompensation {
        if (!$trainer->revenue_share_percent || $trainer->revenue_share_percent <= 0) {
            return null;
        }

        if (!in_array($trainer->compensation_model->value, ['revenue_share', 'hybrid'])) {
            return null;
        }

        if (!(bool) $this->settings->get('revenue_share_enabled', false)) {
            return null;
        }

        // Sum payments received for groups where trainer is head_trainer
        $userId = $trainer->employee?->user_id;
        if (!$userId) {
            return null;
        }

        // Payroll is academy-wide (see calculatePlayerPay): the trainer's share
        // is of everything their groups took in, and both legs — the groups and
        // the payments against their invoices — have to be read the same way or
        // the percentage is taken of a number that depends on the reader.
        [$groupIds, $revenue] = \App\Domain\Training\Models\TrainingGroup::acrossBranches(function () use ($userId, $periodStart, $periodEnd) {
            $groupIds = \App\Domain\Training\Models\TrainingGroup::where('head_trainer_id', $userId)
                ->pluck('id');

            if ($groupIds->isEmpty()) {
                return [$groupIds, 0];
            }

            // Get revenue from invoices paid during this period for these groups
            $revenue = \App\Domain\Financial\Models\Payment::whereHas('invoice', function ($q) use ($groupIds) {
                $q->whereHas('items', function ($q2) use ($groupIds) {
                    $q2->whereIn('metadata->training_group_id', $groupIds);
                });
            })
                ->whereBetween('created_at', [$periodStart, $periodEnd])
                ->where('status', 'confirmed')
                ->sum('amount');

            return [$groupIds, $revenue];
        });

        if ($groupIds->isEmpty()) {
            return null;
        }

        if ($revenue <= 0) {
            return null;
        }

        $shareAmount = (int) round($revenue * $trainer->revenue_share_percent / 100);

        return DB::transaction(function () use ($trainer, $periodStart, $periodEnd, $revenue, $shareAmount, $actor) {
            return TrainerCompensation::create([
                'academy_id'           => $trainer->academy_id,
                'trainer_id'           => $trainer->id,
                'training_session_id'  => null,
                'attendance_record_id' => null,
                'date'                 => $periodEnd->toDateString(),
                'type'                 => CompensationType::RevenueShare,
                'description'          => "حصة إيرادات ({$trainer->revenue_share_percent}%)",
                'quantity'             => 1,
                'rate'                 => $revenue,
                'amount'               => $shareAmount,
                'status'               => CompensationStatus::Pending,
                'metadata'             => [
                    'period_start'   => $periodStart->toDateString(),
                    'period_end'     => $periodEnd->toDateString(),
                    'total_revenue'  => $revenue,
                    'share_percent'  => $trainer->revenue_share_percent,
                ],
                'created_by'           => $actor->id,
            ]);
        });
    }

    /**
     * For hourly trainers, quantity = session duration in hours.
     * For all others, quantity = 1 (one session).
     */
    private function resolveSessionQuantity(Trainer $trainer, int $trainingSessionId): float
    {
        if ($trainer->compensation_model->value !== 'hourly') {
            return 1;
        }

        $session = TrainingSession::with('group.program')->find($trainingSessionId);
        if (!$session) {
            return 1;
        }

        // Prefer actual times if available, then scheduled times, then program default
        $start = $session->actual_start_time ?? $session->start_time;
        $end = $session->actual_end_time ?? $session->end_time;

        if ($start && $end) {
            $minutes = Carbon::parse($start)->diffInMinutes(Carbon::parse($end));
            if ($minutes > 0) {
                return round($minutes / 60, 2);
            }
        }

        // Fallback to program's session_duration_minutes
        $programMinutes = $session->group?->program?->session_duration_minutes;
        if ($programMinutes && $programMinutes > 0) {
            return round($programMinutes / 60, 2);
        }

        return 1;
    }

    /**
     * Resolve the per-session rate for a trainer based on compensation model.
     */
    private function resolveSessionRate(Trainer $trainer): int
    {
        return match ($trainer->compensation_model->value) {
            'hourly'       => $trainer->hourly_rate ?? 0,
            'per_session'  => $trainer->session_rate ?? 0,
            'per_group'    => $trainer->group_rate ?? 0,
            'hybrid'       => $trainer->session_rate ?? $trainer->hourly_rate ?? 0,
            'salary'       => 0, // Salaried trainers don't get per-session pay
            'contract'     => $trainer->session_rate ?? 0,
            default        => $trainer->session_rate ?? 0,
        };
    }
}
