<?php

use App\Livewire\Admin\CustomerDetail;
use App\Livewire\Admin\EditCustomer;
use App\Models\Customer;
use App\Models\User;
use Illuminate\Support\Facades\File;
use Livewire\Livewire;

/**
 * Correcting a customer's own details from the console.
 *
 * It could show them and not touch them, so a moved office meant impersonating
 * the customer or editing the database by hand. Most of what is tested here is
 * not the saving — it is what saving must NOT quietly do.
 */
beforeEach(function () {
    $this->customer = Customer::factory()->create([
        'name' => 'Beispiel GmbH',
        'contact_name' => 'Bea Berger',
        'email' => 'bea@beispiel.test',
        'phone' => '+43 1 111',
        'customer_type' => Customer::TYPE_BUSINESS,
        'vat_id' => 'ATU11111111',
        'vat_id_verified_at' => now(),
        'vat_id_verified_value' => 'ATU11111111',
        'billing_address' => "Alte Gasse 1\n1010 Wien",
    ]);
});

/** The modal, mounted on this customer. */
function editing(Customer $customer, string $role = 'Owner')
{
    return Livewire::actingAs(operator($role), 'operator')
        ->test(EditCustomer::class, ['uuid' => $customer->uuid]);
}

it('saves the details an operator corrected', function () {
    editing($this->customer)
        ->set('name', 'Beispiel GmbH & Co KG')
        ->set('contactName', 'Bernd Berger')
        ->set('phone', '+43 1 999')
        ->set('billingStreet', 'Neue Gasse 9')
        ->set('billingPostcode', '1020')
        ->set('billingCity', 'Wien')
        ->set('locale', 'en')
        ->call('save')
        ->assertHasNoErrors();

    $fresh = $this->customer->fresh();

    expect($fresh->name)->toBe('Beispiel GmbH & Co KG')
        ->and($fresh->contact_name)->toBe('Bernd Berger')
        ->and($fresh->phone)->toBe('+43 1 999')
        ->and($fresh->billing_address)->toContain('1020 Wien')
        ->and($fresh->locale)->toBe('en');
});

it('never resets a recorded customer type back to nobody asked', function () {
    // That answer decides whether a withdrawal right exists. A form saved with
    // the field untouched must not quietly undo it — and the field opens on
    // "leave unchanged", so saving anything else would do exactly that.
    editing($this->customer)->set('customerType', '')->call('save');

    expect($this->customer->fresh()->customer_type)->toBe(Customer::TYPE_BUSINESS);
});

it('records the other answer when that is what was chosen', function () {
    editing($this->customer)->set('customerType', Customer::TYPE_CONSUMER)->call('save');

    expect($this->customer->fresh()->isConsumer())->toBeTrue();
});

it('treats a changed VAT number as unverified, and keeps what was checked', function () {
    // The verification is bound to the VALUE, so a new number is unverified the
    // moment it is stored. The record of what WAS checked stays: clearing it
    // would destroy the evidence rather than the claim.
    editing($this->customer)->set('vatId', 'ATU22222222')->call('save');

    $fresh = $this->customer->fresh();

    expect($fresh->vat_id)->toBe('ATU22222222')
        ->and($fresh->hasVerifiedVatId())->toBeFalse()
        ->and($fresh->vat_id_verified_value)->toBe('ATU11111111');
});

it('leaves a verification alone when the number was not touched', function () {
    editing($this->customer)->set('phone', '+43 1 222')->call('save');

    expect($this->customer->fresh()->hasVerifiedVatId())->toBeTrue();
});

// ---- The address, which is also the login ----

it('moves the portal login with the address and asks for it to be confirmed', function () {
    // Otherwise the customer is locked out of an account that no longer matches
    // their record. And the new address is unconfirmed by definition — nobody
    // has shown they can read it.
    $user = User::factory()->create(['email' => 'bea@beispiel.test', 'email_verified_at' => now()]);
    $this->customer->update(['user_id' => $user->id]);

    editing($this->customer->fresh())->set('email', 'neu@beispiel.test')->call('save')->assertHasNoErrors();

    expect($this->customer->fresh()->email)->toBe('neu@beispiel.test')
        ->and($user->fresh()->email)->toBe('neu@beispiel.test')
        ->and($user->fresh()->email_verified_at)->toBeNull();
});

it('refuses an address that belongs to an operator', function () {
    // R21: a customer and an operator must never share an address. No index
    // spans the two tables, so this is checked rather than left to the database.
    $operator = operator('Support');

    editing($this->customer)
        ->set('email', $operator->email)
        ->call('save')
        ->assertHasErrors('email');

    expect($this->customer->fresh()->email)->toBe('bea@beispiel.test');
});

it('refuses an address that belongs to another customer', function () {
    // Two customers on one address makes every lookup that matches by address —
    // inbound mail, the portal user, the register — ambiguous.
    Customer::factory()->create(['email' => 'schon@da.test']);

    editing($this->customer)
        ->set('email', 'schon@da.test')
        ->call('save')
        ->assertHasErrors('email');

    expect($this->customer->fresh()->email)->toBe('bea@beispiel.test');
});

it('refuses an address that belongs to somebody else user account', function () {
    User::factory()->create(['email' => 'fremd@example.test']);

    editing($this->customer)
        ->set('email', 'fremd@example.test')
        ->call('save')
        ->assertHasErrors('email');
});

it('lets a customer keep their own address while something else is corrected', function () {
    // The unique rule has to ignore this record, or nothing else on the form can
    // ever be saved.
    editing($this->customer)->set('name', 'Anderer Name')->call('save')->assertHasNoErrors();

    expect($this->customer->fresh()->name)->toBe('Anderer Name');
});

it('lowercases the address, because that is how every lookup asks for it', function () {
    editing($this->customer)->set('email', 'GROSS@Beispiel.test')->call('save');

    expect($this->customer->fresh()->email)->toBe('gross@beispiel.test');
});

// ---- The rules around it ----

it('refuses an empty company name rather than storing one', function () {
    editing($this->customer)->set('name', '')->call('save')->assertHasErrors('name');

    expect($this->customer->fresh()->name)->toBe('Beispiel GmbH');
});

it('takes no language this application does not have', function () {
    editing($this->customer)->set('locale', 'fr')->call('save')->assertHasErrors('locale');
});

it('keeps the form to operators who may manage customers', function () {
    Livewire::actingAs(operator('Read-only'), 'operator')
        ->test(EditCustomer::class, ['uuid' => $this->customer->uuid])
        ->assertForbidden();
});

it('is reached from the customer page through openModal, not a page method', function () {
    // R20: an edit button that calls a method on the page component is the inline
    // editor coming back under a new name.
    $page = File::get(resource_path('views/livewire/admin/customer-detail.blade.php'));

    expect($page)->toContain("component: 'admin.edit-customer'");

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(CustomerDetail::class, ['uuid' => $this->customer->uuid])
        ->assertSee(__('customer_detail.edit'));
});

it('does not offer to change what an operator must not change here', function () {
    // status has its own action with its own consequences for access;
    // stripe_customer_id points at somebody else's payments if it is wrong; the
    // brand fields are the customer's own.
    // Comments stripped first. The class documents at length WHY each of these
    // is absent, and reading that documentation as the offence is a trap this
    // repository has fallen into three times — see the @vite, prefers-color-scheme
    // and clupilot.cloud scans.
    $code = implode('', array_map(
        fn ($token) => is_array($token)
            ? (in_array($token[0], [T_COMMENT, T_DOC_COMMENT], true) ? '' : $token[1])
            : $token,
        token_get_all(File::get(app_path('Livewire/Admin/EditCustomer.php'))),
    ));

    expect($code)->not->toContain("'status'")
        ->not->toContain('stripe_customer_id')
        ->not->toContain('brand_logo_path')
        // And the fields it DOES write are the ones it is meant to.
        ->toContain("'billing_address'")
        ->toContain("'customer_type'");
});
