<?php

namespace App\Http\Controllers\Api\V1\Commissions;

use App\Domain\Agents\Models\Agent;
use App\Domain\Commissions\Enums\CommissionStatus;
use App\Domain\Commissions\Models\Commission;
use App\Domain\Commissions\Services\CommissionService;
use App\Domain\Identity\Enums\PermissionScope;
use App\Domain\Identity\Services\PermissionScopeResolver;
use App\Http\Controllers\Controller;
use App\Support\ApiResponse;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

/** Earned commission and the agent ledger (§13A). */
class CommissionController extends Controller
{
    public function __construct(
        private readonly CommissionService $commissions,
        private readonly PermissionScopeResolver $scopes,
    ) {}

    public function index(Request $request): JsonResponse
    {
        abort_unless($request->user()->can('commission.view'), 403);

        $commissions = $this->visible($request)
            ->with(['agent:id,uuid,name,agent_code', 'applicant:id,uuid,full_name,reference_no'])
            ->when($request->filled('status'), fn ($q) => $q->where('status', $request->string('status')))
            ->when($request->boolean('awaiting_approval'), fn ($q) => $q->awaitingApproval())
            ->when($request->filled('agent'), fn ($q) => $q->whereHas(
                'agent',
                fn ($a) => $a->where('uuid', $request->string('agent')),
            ))
            ->latest('accrued_at')
            ->latest('id')
            ->paginate($request->integer('per_page', 25));

        return ApiResponse::success(
            $commissions->through(fn (Commission $commission) => $this->present($commission)),
            meta: [
                'statuses' => array_map(
                    fn (CommissionStatus $status) => ['value' => $status->value, 'label' => $status->label()],
                    CommissionStatus::cases(),
                ),
            ],
        );
    }

    public function forAgent(Request $request, Agent $agent): JsonResponse
    {
        abort_unless($request->user()->can('commission.view'), 403);
        abort_unless($agent->company_id === $request->user()->company_id, 403);

        // An agent signed in to the portal may only open their own ledger.
        if ($request->user()->user_type === 'agent') {
            abort_unless($agent->user_id === $request->user()->getKey(), 403);
        }

        $currency = $request->string('currency', config('mams.money.base_currency'))->toString();

        $commissions = Commission::where('agent_id', $agent->getKey())
            ->with('applicant:id,uuid,full_name,reference_no')
            ->latest('accrued_at')
            ->limit(200)
            ->get();

        return ApiResponse::success(
            [
                'ledger' => $this->commissions->ledger($agent, $currency),
                'commissions' => $commissions->map(fn (Commission $commission) => $this->present($commission)),
            ],
        );
    }

    public function approve(Request $request, Commission $commission): JsonResponse
    {
        abort_unless($request->user()->can('commission.approve'), 403);
        $this->assertSameCompany($request, $commission);

        $approved = $this->commissions->approve($commission, $request->user());

        return ApiResponse::success($this->present($approved->fresh(['agent'])), 'Commission approved.');
    }

    public function hold(Request $request, Commission $commission): JsonResponse
    {
        abort_unless($request->user()->can('commission.approve'), 403);
        $this->assertSameCompany($request, $commission);

        $validated = $request->validate(['reason' => ['required', 'string', 'max:1000']]);

        $held = $this->commissions->hold($commission, $validated['reason'], $request->user());

        return ApiResponse::success($this->present($held->fresh(['agent'])), 'Commission held for review.');
    }

    public function reverse(Request $request, Commission $commission): JsonResponse
    {
        abort_unless($request->user()->can('commission.reverse'), 403);
        $this->assertSameCompany($request, $commission);

        $validated = $request->validate(['reason' => ['required', 'string', 'max:1000']]);

        $reversal = $this->commissions->reverse($commission, $validated['reason'], $request->user());

        return ApiResponse::success(
            $this->present($reversal->fresh(['agent'])),
            'Commission clawed back. The original record is kept.',
        );
    }

    /**
     * Commission belongs to agents, not to staff, so "own" and "team" are
     * resolved through the agent rather than through a user column. An agent
     * signed in to the portal only ever sees their own earnings (§13A).
     */
    private function visible(Request $request): Builder
    {
        $user = $request->user();
        $query = Commission::query()->where('company_id', $user->company_id);

        if ($user->user_type === 'agent') {
            return $query->where('agent_id', Agent::where('user_id', $user->getKey())->value('id') ?? 0);
        }

        return match ($this->scopes->scopeFor($user, 'commission.view')) {
            null => $query->whereRaw('1 = 0'),
            PermissionScope::Global, PermissionScope::Company => $query,
            PermissionScope::Branch => $query->where('branch_id', $user->branch_id),
            PermissionScope::Team => $query->whereHas(
                'agent',
                fn (Builder $agents) => $agents->where('team_id', $user->team_id),
            ),
            // A staff member scoped to "own" holds no commission of their own.
            PermissionScope::Own, PermissionScope::Assigned => $query->whereRaw('1 = 0'),
        };
    }

    private function assertSameCompany(Request $request, Commission $commission): void
    {
        abort_unless($commission->company_id === $request->user()->company_id, 403);
    }

    /** @return array<string, mixed> */
    private function present(Commission $commission): array
    {
        return [
            'id' => $commission->uuid,
            'commission_no' => $commission->commission_no,
            'status' => $commission->status->value,
            'status_label' => $commission->status->label(),
            'is_reversal' => $commission->isReversal(),
            'trigger_event' => $commission->trigger_event->value,
            'trigger_label' => $commission->trigger_event->label(),
            'calculation_basis' => $commission->calculation_basis->value,
            'eligible_amount' => (int) $commission->eligible_amount,
            'rate_basis_points' => (int) $commission->rate_basis_points,
            'commission_amount' => (int) $commission->commission_amount,
            'paid_amount' => (int) $commission->paid_amount,
            'currency' => $commission->currency,
            'formatted_amount' => $commission->money()->format(),
            'calculation_breakdown' => $commission->calculation_breakdown,
            'reversal_reason' => $commission->reversal_reason,
            'accrued_at' => $commission->accrued_at?->toIso8601String(),
            'approved_at' => $commission->approved_at?->toIso8601String(),
            'agent' => $commission->relationLoaded('agent') && $commission->agent ? [
                'id' => $commission->agent->uuid,
                'name' => $commission->agent->name,
                'code' => $commission->agent->agent_code,
            ] : null,
            'applicant' => $commission->relationLoaded('applicant') && $commission->applicant ? [
                'id' => $commission->applicant->uuid,
                'name' => $commission->applicant->full_name,
                'reference' => $commission->applicant->reference_no,
            ] : null,
        ];
    }
}
