<?php

use App\Livewire\Admin\CloseDunningCase;
use App\Livewire\Admin\ExtendDunningDeadline;
use App\Livewire\Admin\PaymentProblems;
use App\Models\Customer;
use App\Models\DunningCase;
use App\Models\Subscription;
use App\Services\Billing\DunningSchedule;
use Illuminate\Support\Carbon;
use Livewire\Livewire;

/**
 * Die zwei Eingriffe, die ein Mensch braucht, und die Einstellungen.
 *
 * Ohne sie ist der Mahnlauf nach dem ersten Telefonat im Weg: der Kunde sagt
 * „ich überweise morgen", und die Automatik schaltet trotzdem am Freitag ab.
 */
function openCase(int $level = 1): DunningCase
{
    $customer = Customer::factory()->create(['name' => 'Berger GmbH', 'stripe_customer_id' => 'cus_42']);
    $subscription = Subscription::factory()->create([
        'customer_id' => $customer->id, 'stripe_subscription_id' => 'sub_42',
    ]);

    return DunningCase::query()->create([
        'subscription_id' => $subscription->id,
        'stripe_invoice_id' => 'in_1',
        'level' => $level,
        'opened_at' => Carbon::now()->subDays(3),
        'next_step_at' => Carbon::now()->addDay(),
        'fee_invoice_ids' => [],
    ]);
}

// ---- Frist verlängern ----------------------------------------------------

it('pushes the deadline out and says who did it and why', function () {
    $case = openCase();
    $vorher = $case->next_step_at;

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(PaymentProblems::class)
        ->call('onExtended', $case->id, 7, 'Kunde überweist bis Freitag')
        ->assertHasNoErrors();

    $case->refresh();

    expect($case->next_step_at->isAfter($vorher->copy()->addDays(6)))->toBeTrue()
        ->and($case->note)->toContain('Freitag');
});

it('does not push a case that is already suspended', function () {
    // Ab der Sperre gibt es keine nächste Frist mehr — die Cloud steht, und
    // was jetzt hilft, ist Bezahlen oder das Schliessen von Hand.
    $case = openCase(DunningSchedule::SUSPENDED);
    $case->update(['next_step_at' => null]);

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(PaymentProblems::class)
        ->call('onExtended', $case->id, 7, 'egal')
        ->assertHasErrors();

    expect($case->fresh()->next_step_at)->toBeNull();
});

// ---- Fall von Hand schliessen -------------------------------------------

it('closes a case by hand, with a reason', function () {
    // Der Fall, für den es das gibt: jemand hat überwiesen statt die Karte zu
    // reparieren. Stripe weiss davon nichts, und die Automatik käme nie zur
    // Ruhe.
    $case = openCase();

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(PaymentProblems::class)
        ->call('onClosed', $case->id, 'Überweisung am 30.7. eingegangen')
        ->assertHasNoErrors();

    $case->refresh();

    expect($case->settled_at)->not->toBeNull()
        ->and($case->note)->toContain('Überweisung');
});

it('refuses to close without a reason', function () {
    // „Erledigt" ohne Begründung ist beim nächsten Nachfragen wertlos — und
    // hier hängt Geld daran, das niemand mehr eintreibt.
    $case = openCase();

    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(PaymentProblems::class)
        ->call('onClosed', $case->id, '   ')
        ->assertHasErrors();

    expect($case->fresh()->settled_at)->toBeNull();
});

it('keeps both interventions behind billing.manage', function () {
    $case = openCase();

    Livewire::actingAs(operator('Support'), 'operator')
        ->test(ExtendDunningDeadline::class, ['id' => $case->id])
        ->assertForbidden();

    Livewire::actingAs(operator('Support'), 'operator')
        ->test(CloseDunningCase::class, ['id' => $case->id])
        ->assertForbidden();
});

// ---- Einstellungen -------------------------------------------------------

it('lets the console set the deadlines and the fees', function () {
    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(PaymentProblems::class)
        ->set('days', [0, 5, 12, 20, 30])
        ->set('feeFromLevel', 3)
        // EURO ins Feld, Cent in die Datenbank. Hier stand einmal 500 und 1500 —
        // also Cent —, weil das Feld selbst in Cent rechnete. Wer eine
        // Mahngebuehr eintraegt, denkt aber in Euro und tippt „5"; daraus wurden
        // fuenf Cent, ohne dass etwas widersprochen haette.
        ->set('fees', [2 => '5', 3 => '15'])
        ->call('saveSchedule')
        ->assertHasNoErrors();

    expect(DunningSchedule::dayOfLevel(1))->toBe(5)
        ->and(DunningSchedule::feeFromLevel())->toBe(3)
        // Stufe 2 kostet nichts, weil die Gebuehr erst ab Stufe 3 greift.
        ->and(DunningSchedule::feeCents(2))->toBe(0)
        ->and(DunningSchedule::feeCents(3))->toBe(1500);
});

it('refuses a schedule that runs backwards', function () {
    // Rückwärts laufende Fristen sind kein Zeitplan — der Tageslauf spränge
    // über Stufen. Abgelehnt und gesagt, nicht still sortiert: wer 20/5/12
    // eintippt, hat sich vertan und soll es sehen.
    Livewire::actingAs(operator('Owner'), 'operator')
        ->test(PaymentProblems::class)
        ->set('days', [0, 20, 5, 12, 30])
        ->call('saveSchedule')
        ->assertHasErrors('days');
});

it('does not let the settings be changed without billing.manage', function () {
    Livewire::actingAs(operator('Support'), 'operator')
        ->test(PaymentProblems::class)
        ->assertForbidden();
});
