<?php

namespace App\Livewire\Admin;

use App\Models\Customer;
use App\Models\DpaVersion;
use App\Services\Legal\ProcessingAgreement;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Livewire\Component;
use Livewire\WithFileUploads;

/**
 * The data-processing agreement: upload a version, publish it, see who has it.
 *
 * The TEXT is not this application's and never will be. Art. 28(3) GDPR wants a
 * contract between the controller (the customer) and the processor (us); what
 * belongs in it is a lawyer's answer, and inventing one here would be worse than
 * having none. What this page does is the part software can do properly: keep
 * the document, say which version is in force, hand it to the customer, and hold
 * the evidence that they accepted it.
 *
 * ## Publishing is what asks every customer again
 *
 * Acceptance is per VERSION. Publishing a new one therefore leaves every
 * customer outstanding until they accept it, which is the correct behaviour and
 * an expensive one to trigger by accident — so a draft is uploaded first and
 * published deliberately, in two steps.
 */
#[Layout('layouts.admin')]
class ProcessingAgreements extends Component
{
    use WithFileUploads;

    /** What the document itself calls this version — "1.0", "2026-08". */
    public string $version = '';

    public string $note = '';

    /** The agreement, and the technical and organisational measures beside it. */
    public $agreement = null;

    public $measures = null;

    public function mount(): void
    {
        $this->authorize('dpa.manage');
    }

    public function upload(): void
    {
        $this->authorize('dpa.manage');

        $data = $this->validate([
            'version' => 'required|string|max:64|unique:dpa_versions,version',
            'note' => 'nullable|string|max:2000',
            // PDF only, and a real size limit: this is handed to customers as
            // the contract between us, not as an editable file.
            'agreement' => 'required|file|mimes:pdf|max:10240',
            'measures' => 'nullable|file|mimes:pdf|max:10240',
        ]);

        // The private DISK. An agreement is not a public asset, and a guessable
        // URL to one would be a list of who our customers are — the root of this
        // disk is outside the document root and nothing here is reachable except
        // through the two routes that check who is asking.
        //
        // The visibility below is the file MODE, not the web: 0755/0644 rather
        // than 0700/0600, so a document written by one process (an artisan run
        // as root) stays readable by the other (PHP-FPM as www-data). Getting
        // that wrong produced a directory nothing could enter and a page whose
        // every link 404'd silently.
        $version = DpaVersion::query()->create([
            'version' => $data['version'],
            'note' => $data['note'] ?: null,
            'agreement_path' => $this->agreement->store('dpa', 'local', 'public'),
            'measures_path' => $this->measures?->store('dpa', 'local', 'public'),
            'created_by' => Auth::guard('operator')->id(),
        ]);

        $this->reset('version', 'note', 'agreement', 'measures');

        $this->dispatch('notify', message: __('dpa_admin.uploaded', ['version' => $version->version]));
    }

    /**
     * Put a version in force.
     *
     * Deliberately separate from the upload: every customer who had accepted the
     * previous one is outstanding again from this moment, and that is not
     * something to do by dropping a file on a form.
     */
    #[On('dpa-publish-confirmed')]
    public function publish(string $uuid): void
    {
        $this->authorize('dpa.manage');

        $version = DpaVersion::query()->where('uuid', $uuid)->firstOrFail();

        if ($version->isPublished()) {
            return;
        }

        $version->update(['published_at' => now()]);

        $this->dispatch('notify', message: __('dpa_admin.published', ['version' => $version->version]));
    }

    public function render()
    {
        $this->authorize('dpa.manage');

        $current = app(ProcessingAgreement::class)->current();

        // How many customers are outstanding on the version in force. Counted
        // against customers who are actually still customers — a closed record
        // is not somebody we are waiting on.
        $customers = Customer::query()->whereNull('closed_at')->count();
        $accepted = $current === null ? 0 : $current->acceptances()->count();

        return view('livewire.admin.processing-agreements', [
            'versions' => DpaVersion::query()->withCount('acceptances')->latest('id')->get(),
            'current' => $current,
            'customers' => $customers,
            'accepted' => $accepted,
            'outstanding' => max(0, $customers - $accepted),
        ]);
    }

    /** Whether a stored file is actually there, for the list. */
    public function exists(?string $path): bool
    {
        return $path !== null && Storage::disk('local')->exists($path);
    }
}
