<?php

namespace App\Livewire\Admin;

use App\Mail\OperatorMessageMail;
use App\Models\Customer;
use App\Models\InboundMail;
use App\Models\Instance;
use App\Models\Invoice;
use App\Models\MailTemplate;
use App\Models\Order;
use App\Models\SecurityBlock;
use App\Models\SentMail;
use App\Models\Subscription;
use App\Models\SupportRequest;
use App\Services\Mail\MailTemplateRenderer;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Mail;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Livewire\Attributes\Url;
use Livewire\Attributes\Validate;
use Livewire\Component;

/**
 * One customer: who they are, what they bought, what they paid, what was said.
 *
 * The first version of this page was a compose box with three lists under it,
 * and the reported complaint was exactly right — it showed nothing ABOUT the
 * customer. An operator on the phone needs the address, the package, whether
 * the last invoice was paid and what was already said to them, and none of that
 * was here.
 *
 * TABBED, like Settings and Integrations, for the same reason: this is four
 * different questions, and stacking them makes a page nobody reads to the
 * bottom. Writing is the LAST tab, because it is the thing you do after
 * reading — and it is where the templates come in, so the same answer is not
 * retyped differently for every customer.
 *
 * The open tab is in the query string: a reload, a bookmark and the back button
 * all land where the operator was, and a link can point straight at the tab
 * that matters ("…/customers/x?tab=billing").
 */
#[Layout('layouts.admin')]
class CustomerDetail extends Component
{
    /**
     * The tabs, in order. The list IS the schema: it validates the query
     * string, builds the bar and decides what renders.
     */
    public const TABS = ['overview', 'package', 'billing', 'messages', 'compose'];

    public Customer $customer;

    /**
     * Which tab is open, in the address bar.
     *
     * `history: true`, so each tab is a step the back button can take — and no
     * `except`, so the parameter is there from the first render rather than
     * appearing only once somebody leaves the default tab. Hiding it on the
     * default read as "the tab is not in the URL", which is exactly how it was
     * reported: a link to this page could not say which tab it meant unless the
     * tab happened not to be the first one.
     */
    #[Url(history: true)]
    public string $tab = 'overview';

    // ---- Writing to them ----

    #[Validate('required|string|max:200')]
    public string $subject = '';

    #[Validate('required|string|min:3|max:5000')]
    public string $body = '';

    /**
     * The request being answered, if this message is an answer to one.
     *
     * Answering marks it answered — which is the whole reason to answer from
     * here rather than from a mail client, where the request stays open for
     * ever because nothing ever told it otherwise.
     */
    public ?string $answering = null;

    public function mount(string $uuid): void
    {
        $this->authorize('customers.manage');

        $this->customer = Customer::query()->where('uuid', $uuid)->firstOrFail();

        if (! in_array($this->tab, self::TABS, true)) {
            $this->tab = self::TABS[0];
        }
    }

    /**
     * Put a template into the fields, filled in for THIS customer.
     *
     * Inserted, never sent: the placeholders come out filled and the last two
     * sentences are still the operator's to write. What goes out is what they
     * saw and edited.
     */
    public function useTemplate(string $uuid): void
    {
        $this->authorize('customers.manage');

        $template = MailTemplate::query()->where('uuid', $uuid)->where('active', true)->first();

        if ($template === null) {
            return;
        }

        $filled = app(MailTemplateRenderer::class)->render(
            $template,
            $this->customer,
            Auth::guard('operator')->user(),
        );

        // The subject is only taken when the operator has not written one — a
        // template that overwrites what somebody typed is a template that eats
        // work. The body is replaced, because that is what "use this template"
        // means and the field is empty in the case that matters.
        if (trim($this->subject) === '') {
            $this->subject = $filled['subject'];
        }

        $this->body = $filled['body'];
        $this->tab = 'compose';
    }

    /** Start an answer to one request: subject prefilled, the request remembered. */
    public function answer(string $uuid): void
    {
        $this->authorize('customers.manage');

        $request = $this->customer->supportRequests()->where('uuid', $uuid)->first();

        if ($request === null) {
            return;
        }

        $this->answering = $request->uuid;
        $this->subject = __('customer_message.re', ['subject' => $request->subject]);
        $this->body = '';
        $this->tab = 'compose';
    }

    public function cancelAnswer(): void
    {
        $this->answering = null;
        $this->subject = '';
        $this->body = '';
    }

    /**
     * ConfirmReleaseBlock (Konsole) dispatches this back (R23) — see that
     * class. Es mutiert nichts; die eigentliche Freigabe UND die Pruefung,
     * dass die Sperre wirklich an EINER Instanz DIESES Kunden haengt, stehen
     * hier — ein Modal ist ohne die Route-Middleware dieser Seite erreichbar
     * (R20).
     */
    #[On('security-block-release-confirmed')]
    public function onSecurityBlockReleaseConfirmed(string $uuid): void
    {
        $this->authorize('instances.manage');

        $block = SecurityBlock::query()
            ->where('uuid', $uuid)
            ->whereHas('instance', fn ($q) => $q->where('customer_id', $this->customer->id))
            ->first();

        if ($block === null) {
            return;
        }

        // Siehe SecurityBlock::release() — der Rueckgabewert sagt, ob die
        // Firewall schon nachgezogen hat, nicht ob der Datensatz steht.
        $this->dispatch('notify', message: $block->release(auth('operator')->user())
            ? __('admin.security_block.released')
            : __('admin.security_block.released_pending'));
    }

    /**
     * Send it, record it, and close the request it answers.
     *
     * The register row is written here rather than left to the MessageSent
     * listener alone, because this is the one mail whose BODY is worth keeping:
     * an operator typed it, and "what exactly did I write to them in March" is a
     * question the mail server's log cannot answer either.
     */
    public function send(): void
    {
        $this->authorize('customers.manage');

        $data = $this->validate();

        $mail = new OperatorMessageMail($this->customer, $data['subject'], $data['body']);
        Mail::mailer($mail->mailer)->to($this->customer->email)->send($mail);

        SentMail::query()
            ->where('customer_id', $this->customer->id)
            ->where('to', $this->customer->email)
            ->latest('id')
            ->limit(1)
            ->update(['from_operator' => true, 'body' => $data['body']]);

        if ($this->answering !== null) {
            $this->customer->supportRequests()
                ->where('uuid', $this->answering)
                ->update(['status' => 'answered', 'answered_at' => now()]);
        }

        $this->cancelAnswer();
        $this->dispatch('notify', message: __('customer_message.sent'));
    }

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

        $instance = Instance::query()
            ->where('customer_id', $this->customer->id)
            ->latest('id')
            ->first();

        return view('livewire.admin.customer-detail', [
            'tabs' => self::TABS,
            'subscription' => $this->subscription(),
            'instance' => $instance,
            // Sicherheitssperren DIESER Instanz — derselbe Abschnitt (Zeilen
            // + Knopf) wie auf der Host-Detailseite, hinter instances.manage
            // statt hosts.manage.
            'securityBlocks' => $instance
                ? SecurityBlock::query()->where('instance_id', $instance->id)->orderByDesc('blocked_at')->get()
                : collect(),
            'orders' => Order::query()
                ->where('customer_id', $this->customer->id)
                ->latest('id')
                ->limit(20)
                ->get(),
            'invoices' => Invoice::query()
                ->where('customer_id', $this->customer->id)
                ->orderByDesc('issued_on')
                ->orderByDesc('id')
                ->limit(20)
                ->get(),
            'requests' => $this->customer->supportRequests()->latest('id')->get(),
            'inbound' => InboundMail::query()
                ->where('customer_id', $this->customer->id)
                ->latest('received_at')
                ->limit(30)
                ->get(),
            'mails' => SentMail::query()
                ->where('customer_id', $this->customer->id)
                ->latest('sent_at')
                ->limit(30)
                ->get(),
            'templates' => MailTemplate::query()->usable()->get(),
            // Counted for the tab bar, so an operator sees where the unread
            // things are before opening anything.
            'openRequests' => $this->customer->supportRequests()
                ->whereNotIn('status', SupportRequest::CLOSED)
                ->count(),
        ]);
    }

    /** The contract that is being billed, or the most recent one there was. */
    private function subscription(): ?Subscription
    {
        return Subscription::query()
            ->where('customer_id', $this->customer->id)
            ->with('addons')
            ->orderByRaw("status = 'active' DESC")
            ->latest('id')
            ->first();
    }
}
