<?php

namespace App\Livewire;

use App\Actions\RestartInstance;
use App\Livewire\Concerns\ResolvesCustomer;
use App\Models\Instance;
use App\Models\MaintenanceWindow;
use App\Services\Billing\StorageAllowance;
use App\Support\ProvisioningSettings;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Support\Number;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Livewire\Component;

#[Layout('layouts.portal-app')]
class Cloud extends Component
{
    use ResolvesCustomer;

    /**
     * Restart the customer's own cloud, once ConfirmRestartCloud has said so.
     *
     * The modal raises the event and this method does the work, so the rule
     * about who may restart what stays in one place (R23). That place is
     * RestartInstance itself, not this method: the instance below is resolved
     * from the signed-in customer, so it is already theirs, but the action
     * re-checks anyway — a Livewire method is reachable by anyone who can POST
     * to /livewire/update, and an authorisation that lives only in how a caller
     * happened to look something up is one refactor away from being gone.
     */
    #[On('cloud-restart-confirmed')]
    public function restart(): void
    {
        $instance = $this->instance();

        if ($instance === null) {
            $this->dispatch('notify', message: __('dashboard.no_customer_action'));

            return;
        }

        try {
            $run = app(RestartInstance::class)($instance);
        } catch (AuthorizationException) {
            $this->dispatch('notify', message: __('cloud.restart_denied'));

            return;
        }

        // Null is every ordinary "not now": a machine still being built, one
        // whose service has ended, or one with a run already in flight. The
        // customer gets told to try again rather than being left with a button
        // that appears to have done nothing.
        $this->dispatch('notify', message: __($run === null ? 'cloud.restart_busy' : 'cloud.restart_started'));
    }

    /**
     * The customer's service instance — the one the whole page is about.
     *
     * Resolved here rather than inside render() so the restart action and the
     * card cannot end up talking about two different machines.
     */
    private function instance(): ?Instance
    {
        return $this->customer()?->instances()
            ->whereIn('status', ['active', 'provisioning', 'cancellation_scheduled'])
            ->latest('id')->first();
    }

    public function render()
    {
        $locale = app()->getLocale();
        $growth = [180, 188, 195, 199, 204, 210, 214, 219, 223, 228, 231, 235];

        // The card is built from the customer's real service instance, so the
        // maintenance badge below can be scoped to exactly that instance's host.
        $customer = $this->customer();
        $shown = $this->instance();
        $maintenance = MaintenanceWindow::forInstance($shown)->first();

        // The instance carries what was actually provisioned, and the contract
        // what was bought. Neither is the catalogue, which only describes what
        // we would sell someone new — never what this customer already has.
        $contract = $shown?->subscription;
        $planKey = $shown?->plan ?? 'team';
        // The whole allowance, packs included, asked of the one class that adds
        // them up. Reading `quota_gb` here drew the ring against the package
        // alone, so a customer who had bought two packs saw themselves at 90 %
        // full of a quota their cloud was no longer enforcing.
        $quota = StorageAllowance::for($shown)->totalGb() ?: 500;
        // Usage metering is not wired yet — scale the illustrative curve into the
        // instance's quota so the chart and the "x / y GB" label never disagree.
        $curveMax = max($growth);
        if ($curveMax > $quota) {
            $growth = array_map(fn ($v) => (int) round($v / $curveMax * $quota), $growth);
        }
        $used = $growth[count($growth) - 1];
        // address() rather than `custom_domain ?: subdomain`: a domain whose
        // DNS proof has not been read is not this instance's address, and
        // printing it here would tell a customer to visit a hostname that does
        // not resolve to them.
        $domain = $shown?->subdomain
            ? $shown->address(ProvisioningSettings::dnsZone())
            : 'cloud.example.com';

        return view('livewire.cloud', [
            'instance' => [
                'name' => $customer ? __('cloud.instance_name', ['name' => $customer->name]) : __('cloud.title'),
                'domain' => $domain,
                'status' => $shown->status ?? 'active',
                // A granted package shows without a price at all — not "free",
                // not struck through — so a later conversion to paid does not
                // read as a negotiation.
                'plan' => $contract?->isGranted()
                    ? __('cloud.plan_line_granted', ['plan' => 'CluPilot Cloud '.__('billing.plan.'.$planKey)])
                    : __('cloud.plan_line', [
                        'plan' => 'CluPilot Cloud '.__('billing.plan.'.$planKey),
                        // The line ends in "/mo", so a yearly contract has to be
                        // divided down before it goes in.
                        'price' => (int) round(($contract?->monthlyPriceCents() ?? 0) / 100),
                    ]),
                'location' => __('cloud.datacenter'),
                'storageUsed' => $used,
                'storageQuota' => $quota,
                'seats' => __('billing.seats_count', ['count' => $contract?->seats ?? 0]),
                'performance' => __('billing.perf.'.($contract?->performance ?? 'standard')),
            ],
            'storageChart' => [
                'type' => 'line',
                'data' => [
                    'labels' => ['', '', '', '', '', '', '', '', '', '', '', __('cloud.now')],
                    'datasets' => [[
                        'label' => 'GB',
                        'data' => $growth,
                        'borderColor' => 'token:accent',
                        'backgroundColor' => 'token:accent/0.10',
                        'fill' => true,
                        'tension' => 0.35,
                        'pointRadius' => 0,
                        'borderWidth' => 2,
                    ]],
                ],
                'options' => [
                    'scales' => [
                        'x' => ['grid' => ['display' => false]],
                        'y' => ['beginAtZero' => false, 'grid' => ['color' => 'token:border']],
                    ],
                    'plugins' => ['legend' => ['display' => false]],
                ],
            ],
            'storageLabel' => Number::format($used, locale: $locale).' / '.Number::format($quota, locale: $locale).' GB',
            'maintenance' => $maintenance,
            // A plan change writes the new CPU and memory into the VM's
            // configuration and a running guest only takes them at its next cold
            // boot. Nobody restarts somebody's cloud as a side effect of a
            // purchase, so the customer is told instead — otherwise they are
            // paying for cores they cannot see and have no way of finding out.
            'restartPending' => $shown?->restartIsPending() ?? false,
            // Whether there is a machine a restart could reach at all. The
            // button is drawn either way — hiding it would leave a customer
            // wondering where it went during a build — but it is only live when
            // pressing it can do something.
            'canRestart' => $shown?->hasLiveMachine() ?? false,
        ]);
    }
}
