<?php

namespace App\Livewire;

use App\Livewire\Concerns\ResolvesCustomer;
use Illuminate\Support\Facades\Storage;
use Livewire\Attributes\Layout;
use Livewire\Attributes\On;
use Livewire\Attributes\Validate;
use Livewire\Component;
use Livewire\WithFileUploads;

class Settings extends Component
{
    use \App\Livewire\Concerns\ChangesOwnPassword;
    use \App\Livewire\Concerns\ConfirmsPassword;

    /** Shown once, right after setting two-factor up. */
    public ?array $recoveryCodes = null;

    public string $twoFactorCode = '';

    /**
     * Start two-factor setup: generate the secret, then show the QR code.
     *
     * Not confirmed yet — Fortify keeps `two_factor_confirmed_at` null until a
     * code from the app has been accepted, so someone who scans nothing and
     * closes the tab is not left locked out of their own account.
     */
    public function enableTwoFactor(): void
    {
        $this->requireConfirmedPassword();

        app(\Laravel\Fortify\Actions\EnableTwoFactorAuthentication::class)(auth()->user());
    }

    /** Accept a code from the app, which is what actually turns it on. */
    public function confirmTwoFactor(): void
    {
        $this->requireConfirmedPassword();

        try {
            app(\Laravel\Fortify\Actions\ConfirmTwoFactorAuthentication::class)(
                auth()->user(),
                $this->twoFactorCode,
            );
        } catch (\Illuminate\Validation\ValidationException $e) {
            $this->addError('twoFactorCode', $e->errors()['code'][0] ?? __('settings.twofa_code_wrong'));

            return;
        }

        $this->twoFactorCode = '';
        // Shown once, here, because this is the only moment they exist in a
        // form anyone will read. They stay retrievable afterwards, but nobody
        // writes down what they were not shown.
        $this->recoveryCodes = json_decode(decrypt(auth()->user()->two_factor_recovery_codes), true);
        $this->dispatch('notify', message: __('settings.twofa_on'));
    }

    public function regenerateRecoveryCodes(): void
    {
        $this->requireConfirmedPassword();

        app(\Laravel\Fortify\Actions\GenerateNewRecoveryCodes::class)(auth()->user());
        $this->recoveryCodes = json_decode(decrypt(auth()->user()->refresh()->two_factor_recovery_codes), true);
    }

    public function disableTwoFactor(): void
    {
        $this->requireConfirmedPassword();

        app(\Laravel\Fortify\Actions\DisableTwoFactorAuthentication::class)(auth()->user());
        $this->recoveryCodes = null;
        $this->dispatch('notify', message: __('settings.twofa_off'));
    }

    /**
     * The disable button opens ConfirmDisableTwoFactor instead of calling
     * disableTwoFactor() directly (R23); its confirm button dispatches back
     * here, and disableTwoFactor() still runs its own password check.
     */
    #[On('twofa-disable-confirmed')]
    public function onTwoFaDisableConfirmed(): void
    {
        $this->disableTwoFactor();
    }

    /**
     * Every two-factor action goes through this.
     *
     * Server-side, on each call, and not merely by hiding the buttons: a
     * Livewire action is reachable by anyone who can post to /livewire/update,
     * and "the form was not on screen" has never stopped anybody.
     */
    private function requireConfirmedPassword(): void
    {
        abort_unless($this->passwordRecentlyConfirmed(), 403);
    }

    use ResolvesCustomer, WithFileUploads;

    // Company / billing profile
    #[Validate('required|string|max:255')]
    public string $companyName = '';

    #[Validate('nullable|string|max:255')]
    public string $contactName = '';

    #[Validate('nullable|string|max:64')]
    public string $phone = '';

    #[Validate('nullable|string|max:64')]
    public string $vatId = '';

    #[Validate('nullable|string|max:2000')]
    public string $billingAddress = '';

    // Branding
    #[Validate('nullable|string|max:255')]
    public string $brandDisplayName = '';

    #[Validate('nullable|regex:/^#[0-9a-fA-F]{6}$/')]
    public string $brandPrimary = '';

    #[Validate('nullable|regex:/^#[0-9a-fA-F]{6}$/')]
    public string $brandAccent = '';

    /** New logo upload (validated on save). */
    public $logo = null;

    public ?string $brandLogoPath = null;

    public function mount(): void
    {
        $c = $this->customer();
        if ($c === null) {
            return;
        }
        $this->companyName = $c->name ?? '';
        $this->contactName = $c->contact_name ?? '';
        $this->phone = $c->phone ?? '';
        $this->vatId = $c->vat_id ?? '';
        $this->billingAddress = $c->billing_address ?? '';
        $this->brandDisplayName = $c->brand_display_name ?? '';
        $this->brandPrimary = $c->brand_primary_color ?? '';
        $this->brandAccent = $c->brand_accent_color ?? '';
        $this->brandLogoPath = $c->brand_logo_path;
    }

    public function saveProfile(): void
    {
        $c = $this->requireCustomer();
        if ($c === null) {
            return;
        }

        $this->validateOnly('companyName');
        $data = $this->validate([
            'companyName' => 'required|string|max:255',
            'contactName' => 'nullable|string|max:255',
            'phone' => 'nullable|string|max:64',
            'vatId' => 'nullable|string|max:64',
            'billingAddress' => 'nullable|string|max:2000',
        ]);

        $c->update([
            'name' => $data['companyName'],
            'contact_name' => $data['contactName'] ?: null,
            'phone' => $data['phone'] ?: null,
            'vat_id' => $data['vatId'] ?: null,
            'billing_address' => $data['billingAddress'] ?: null,
        ]);

        $this->dispatch('notify', message: __('settings.profile_saved'));
    }

    public function saveBranding(): void
    {
        $c = $this->requireCustomer();
        if ($c === null) {
            return;
        }

        $this->validate([
            'brandDisplayName' => 'nullable|string|max:255',
            'brandPrimary' => 'nullable|regex:/^#[0-9a-fA-F]{6}$/',
            'brandAccent' => 'nullable|regex:/^#[0-9a-fA-F]{6}$/',
            'logo' => 'nullable|image|mimes:png,webp|max:2048',
        ]);

        // Store the new upload, but keep the old file until the DB row that
        // references it is updated — delete the old one only after that commits,
        // so a failed update never orphans a file or dangles a reference.
        $oldToDelete = null;
        if ($this->logo !== null) {
            $oldToDelete = $this->brandLogoPath;
            $this->brandLogoPath = $this->logo->store('branding', 'public');
            $this->logo = null;
        }

        $c->update([
            'brand_display_name' => $this->brandDisplayName ?: null,
            'brand_primary_color' => $this->brandPrimary ?: null,
            'brand_accent_color' => $this->brandAccent ?: null,
            'brand_logo_path' => $this->brandLogoPath,
        ]);

        if ($oldToDelete !== null && $oldToDelete !== $this->brandLogoPath) {
            Storage::disk('public')->delete($oldToDelete);
        }

        $this->dispatch('notify', message: __('settings.branding_saved'));
    }

    public function removeLogo(): void
    {
        $c = $this->requireCustomer();
        if ($c === null) {
            return;
        }
        if ($this->brandLogoPath !== null) {
            Storage::disk('public')->delete($this->brandLogoPath);
        }
        $this->brandLogoPath = null;
        $c->update(['brand_logo_path' => null]);
        $this->dispatch('notify', message: __('settings.branding_saved'));
    }

    #[Layout('layouts.portal-app')]
    public function render()
    {
        $c = $this->customer();
        // Prefer the active/cancelling instance for the package section so the
        // controls line up with what cancellation actually targets.
        $active = $c?->instances()->where('status', 'active')->latest('id')->first();
        $scheduled = $c?->instances()->where('status', 'cancellation_scheduled')->latest('id')->first();
        $instance = $active ?? $scheduled ?? $c?->instances()->latest('id')->first();

        $user = auth()->user();

        return view('livewire.settings', [
            // Never the secret itself — only whether it exists, and the SVG
            // Fortify renders from it. The secret in a Livewire property would
            // travel to the browser and back in the component snapshot.
            'twoFactorPending' => $user->two_factor_secret !== null && $user->two_factor_confirmed_at === null,
            'twoFactorOn' => $user->two_factor_confirmed_at !== null,
            'twoFactorQr' => $user->two_factor_secret !== null && $user->two_factor_confirmed_at === null
                ? $user->twoFactorQrCodeSvg()
                : null,
            'passwordConfirmed' => $this->passwordRecentlyConfirmed(),
            'customer' => $c,
            'instance' => $instance,
            'branding' => $c?->brandingResolved(),
            'logoUrl' => $this->brandLogoPath ? Storage::disk('public')->url($this->brandLogoPath) : null,
            'hasActivePackage' => $active !== null,
            'cancellationScheduled' => $active === null && $scheduled !== null,
        ]);
    }
}
