<?php

namespace App\Livewire\Programs;

use App\Domain\Pricing\Models\BasePrice;
use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Enums\ProgramStatus;
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\TrainingProgramService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Livewire\Concerns\AppliesRoleScope;
use App\Livewire\Concerns\WithSorting;
use App\Models\User;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Attributes\Url;
use Livewire\Component;
use Livewire\WithPagination;

#[Layout('layouts.app')]
#[Title('البرامج التدريبية')]
class ProgramList extends Component
{
    use WithPagination, WithSorting, UsesBranchScope, AppliesRoleScope;

    protected string $scopePermission = 'programs.list';

    #[Url]
    public string $search = '';

    #[Url]
    public string $status = '';

    #[Url]
    public string $activityFilter = '';

    #[Url]
    public string $trainerFilter = '';


    public function updatedSearch(): void
    {
        $this->resetPage();
    }

    public function updatedStatus(): void
    {
        $this->resetPage();
    }

    public function updatedActivityFilter(): void
    {
        $this->trainerFilter = '';
        $this->resetPage();
    }

    public function updatedTrainerFilter(): void
    {
        $this->resetPage();
    }

    public function mount(): void
    {
        if (!request()->has('sortBy')) {
            $this->sortBy = 'name_ar';
        }
        if (!request()->has('sortDir')) {
            $this->sortDir = 'asc';
        }
    }

    public function changeStatus(string $uuid, string $newStatus, TrainingProgramService $service): void
    {
        $this->authorize('programs.update');

        try {
            // The uuid comes from the browser. TrainingProgram carries
            // BranchScope in shared mode, so this resolves to a programme of
            // this branch or an academy-wide one, and 404s otherwise.
            $program = TrainingProgram::where('uuid', $uuid)->firstOrFail();
            $service->changeStatus($program, $newStatus);
            session()->flash('success', __('تم تحديث حالة البرنامج بنجاح'));
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        }
    }

    public function delete(string $uuid, TrainingProgramService $service): void
    {
        $this->authorize('programs.delete');

        try {
            // Same reading as changeStatus(): branch-scoped by the model, and
            // deleting cascades to the programme's groups, so the scope is
            // what keeps another branch's groups out of reach.
            $program = TrainingProgram::where('uuid', $uuid)->firstOrFail();
            $service->delete($program);
            session()->flash('success', __('تم حذف البرنامج بنجاح'));
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        }
    }

    /**
     * The two tier prices of each programme on the page, in piasters.
     *
     * One query for the whole page rather than two per row. The prices are the
     * same rows ProgramForm writes — base_prices tagged with a membership_type
     * in metadata — so what the list shows is exactly what the sale will
     * charge. BasePrice is branch-scoped, so a programme visible from here
     * cannot show another branch's price.
     *
     * @param  array<int>  $programIds
     * @return array<int, array{member: ?int, non_member: ?int}>
     */
    private function pricesFor(array $programIds): array
    {
        if ($programIds === []) {
            return [];
        }

        $rows = BasePrice::query()
            ->where('priceable_type', TrainingProgram::class)
            ->whereIn('priceable_id', $programIds)
            ->where('is_active', true)
            ->get(['priceable_id', 'amount', 'metadata']);

        $prices = [];

        foreach ($rows as $row) {
            $tier = $row->metadata['membership_type'] ?? null;

            if ($tier !== 'member' && $tier !== 'non_member') {
                continue;
            }

            $prices[$row->priceable_id][$tier] = (int) $row->amount;
        }

        return $prices;
    }

    public function render()
    {
        $branchId = $this->getActiveBranchId();

        // No branch clause: TrainingProgram carries BranchScope in shared mode,
        // which is exactly `branch_id = active OR branch_id IS NULL`. Repeating
        // it here would only be a second place to get it wrong.
        $query = TrainingProgram::query()
            ->with(['activity', 'branch'])
            ->withCount('groups')
            ->when($this->search, fn ($q) => $q->where(function ($sub) {
                $sub->where('name_ar', 'ilike', "%{$this->search}%")
                    ->orWhere('name', 'ilike', "%{$this->search}%");
            }))
            ->when($this->status, fn ($q) => $q->where('status', $this->status))
            ->when($this->activityFilter, fn ($q) => $q->where('activity_id', $this->activityFilter))
            ->when($this->trainerFilter, fn ($q) => $q->where('default_trainer_id', $this->trainerFilter))
            ->orderBy($this->sortBy, $this->sortDir);

        $this->applyRoleScope($query);

        // users has no branch_id — a user's branch lives on the role_user
        // pivot — so BranchScope cannot reach this query and the filter has to
        // be written out. A null pivot branch means the user is not pinned
        // anywhere and works for every branch. The programme sub-query needs
        // no branch clause: it is scoped by the model.
        $trainers = User::query()
            ->select('id', 'name_ar', 'name')
            ->whereHas('roles', function ($q) use ($branchId) {
                $q->whereIn('slug', ['trainer', 'head_trainer']);

                if ($branchId !== null) {
                    $q->where(fn ($w) => $w->where('role_user.branch_id', $branchId)
                        ->orWhereNull('role_user.branch_id'));
                }
            })
            ->when($this->activityFilter, fn ($q) => $q->whereIn('id',
                TrainingProgram::where('activity_id', $this->activityFilter)
                    ->whereNotNull('default_trainer_id')
                    ->pluck('default_trainer_id')
            ))
            ->orderBy('name_ar')
            ->get();

        $programs = $query->paginate(15);

        return view('livewire.programs.program-list', [
            'programs' => $programs,
            'prices' => $this->pricesFor($programs->pluck('id')->all()),
            'activities' => Activity::where('is_active', true)->orderBy('name_ar')->get(['id', 'name_ar']),
            'trainers' => $trainers,
            'statusOptions' => [
                'draft' => 'مسودة',
                'active' => 'نشط',
                'full' => 'ممتلئ',
                'closed' => 'مغلق',
                'archived' => 'مؤرشف',
            ],
        ]);
    }
}
