<?php

declare(strict_types=1);

namespace App\Livewire;

use App\Exceptions\ServiceUnavailableForOrderException;
use App\Exceptions\UploadRejectedException;
use App\Models\Service;
use App\Services\Services\ServiceOrderService;
use App\Services\Uploads\UploadService;
use Illuminate\Support\Collection;
use Livewire\Attributes\Computed;
use Livewire\Component;

/**
 * Order a mix or a master.
 *
 * Audio is handed over in one of two ways, and both are first-class: a direct
 * upload straight to storage, or a link to YouTube or BeatStars, which the
 * brief explicitly allows. Neither is treated as the fallback.
 */
class MixMasterOrder extends Component
{
    public ?int $serviceId = null;

    public bool $express = false;

    public string $trackTitle = '';

    public string $notes = '';

    public string $sourceMode = 'upload';

    public string $sourceLink = '';

    public bool $usePackage = false;

    public ?string $error = null;

    public function mount(): void
    {
        $this->serviceId = Service::active()->orderBy('sort_order')->value('id');
    }

    public function selectService(int $serviceId): void
    {
        $this->serviceId = $serviceId;
        $this->error = null;
    }

    #[Computed]
    public function services(): Collection
    {
        return Service::active()->orderBy('sort_order')->get();
    }

    #[Computed]
    public function service(): ?Service
    {
        return $this->serviceId ? $this->services->firstWhere('id', $this->serviceId) : null;
    }

    /** A package with mix credits left on it, if the artist has one. */
    #[Computed]
    public function availablePackage()
    {
        $package = auth()->user()->packagePurchases()
            ->where('status', 'active')
            ->where('expires_at', '>', now())
            ->where('mixmaster_credits_remaining', '>', 0)
            ->orderBy('expires_at')
            ->first();

        if ($package === null || $this->service === null) {
            return null;
        }

        return $package->mixmaster_credits_remaining >= $this->service->package_credits
            ? $package
            : null;
    }

    #[Computed]
    public function activePackage()
    {
        return $this->usePackage ? $this->availablePackage : null;
    }

    #[Computed]
    public function total(): string
    {
        if ($this->service === null) {
            return '0.00';
        }

        // Express is charged even on a package, because it is a real cost to
        // the studio rather than part of what the package bought.
        if ($this->activePackage !== null) {
            return $this->express && $this->service->express_surcharge !== null
                ? (string) $this->service->express_surcharge
                : '0.00';
        }

        return $this->service->priceFor($this->express);
    }

    public function place(ServiceOrderService $orders, UploadService $uploads)
    {
        $this->error = null;

        $this->validate([
            'trackTitle' => ['required', 'string', 'max:120'],
            'sourceLink' => ['nullable', 'string', 'max:1000'],
            'notes' => ['nullable', 'string', 'max:1000'],
        ]);

        if ($this->service === null) {
            $this->error = 'Choose a service first.';

            return null;
        }

        if ($this->sourceMode === 'link' && trim($this->sourceLink) === '') {
            $this->error = 'Paste the link to your track, or switch to uploading a file.';

            return null;
        }

        try {
            $order = $orders->place(
                user: auth()->user(),
                service: $this->service,
                express: $this->express,
                trackTitle: $this->trackTitle,
                package: $this->activePackage,
                notes: $this->notes ?: null,
            );

            if ($this->sourceMode === 'link') {
                $uploads->recordLink($order, 'source_audio', trim($this->sourceLink), auth()->user());
                $orders->sourceReceived($order);
            }
        } catch (ServiceUnavailableForOrderException|UploadRejectedException $e) {
            $this->error = $e->getMessage();

            return null;
        }

        return $this->redirectRoute('mix.order', ['order' => $order->reference], navigate: true);
    }

    public function render()
    {
        return view('livewire.mix-master-order')->layout('layouts.site');
    }
}
