<?php

declare(strict_types=1);

use classes\email;
use classes\stripe;
use classes\stripe_fake_http_client;

putenv('STRIPE_FAKE_MODE=1');
putenv('STRIPE_FAKE_STORE_PATH=' . sys_get_temp_dir() . '/truckwash-stripe-api-tests.json');
putenv('EMAIL_FAKE_MODE=1');

usesApiSuite();

beforeEach(function (): void {
    stripe_fake_http_client::resetStore();
    email::resetFakeDeliveries();

    if (api_tests_enabled()) {
        api_test_runtime()->db()->query("DELETE FROM stripe_module_orders WHERE invoice_id LIKE 'in_fake_%'");
        api_test_runtime()->db()->query("DELETE FROM stripe_module_customers WHERE customer_id LIKE 'cus_fake_%' OR email LIKE 'stripe-%@example.com'");
    }
});

it('returns a setup required error when department terminal readers are requested without terminal setup', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Stripe Setup Pending Department',
    ]);
    $session = api_fixtures()->createUserSession([
        'modules_stripe_department_terminal_readers_list',
        'department_access_' . $department['id'],
    ]);

    $response = api_client()->get(
        '/modules/stripe/department/terminal/readers?id=' . $department['id'],
        $session['headers']
    );

    $response
        ->assertStatus(409)
        ->assertEnvelope()
        ->assertSuccess(false)
        ->assertMessage('Card payments are not ready for this department. Open Stripe setup and choose a terminal location.');

    expect($response->data())
        ->toBeArray()
        ->toHaveKey('code', 'stripe_terminal_setup_required');
});

it('sends a Stripe invoice by email and persists the hosted invoice association', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Stripe Email Payments Department',
    ]);
    $customer = api_fixtures()->createUser([
        'display_name' => 'Stripe Hosted Invoice Customer',
    ]);
    $order = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'reference' => 'STRIPE-EMAIL-SEND',
        'reg_1' => 'EMAIL01',
    ]);
    $session = api_fixtures()->createUserSession([
        'modules_stripe_invoice_send',
    ]);
    $emailAddress = sprintf('stripe-email-%d@example.com', (int)$order['id']);

    $response = api_client()->post('/modules/stripe/invoice', [
        'email' => $emailAddress,
        'order_id' => $order['id'],
    ], $session['headers']);

    $response
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess(true);

    expect($response->data())
        ->toBeArray()
        ->toHaveKey('id')
        ->toHaveKey('hosted_invoice_url')
        ->toHaveKey('metadata');

    expect($response->data()['metadata'] ?? [])
        ->toMatchArray([
            'order_id' => (string)$order['id'],
            'customer_id' => (string)$customer['customer_number'],
            'department_id' => (string)$department['id'],
            'reference' => 'STRIPE-EMAIL-SEND',
            'reg_1' => 'EMAIL01',
        ])
        ->and(($response->data()['metadata']['stripe_customer_id'] ?? null))
        ->toBe((string)($response->data()['customer'] ?? ''));

    $retrievedInvoice = (new stripe())->invoice->retrieve((string)$response->data()['id']);
    $retrievedMetadata = json_decode(json_encode($retrievedInvoice->metadata), true);
    if (!is_array($retrievedMetadata)) {
        $retrievedMetadata = [];
    }

    expect($retrievedMetadata)
        ->toMatchArray([
            'order_id' => (string)$order['id'],
            'customer_id' => (string)$customer['customer_number'],
            'department_id' => (string)$department['id'],
            'reference' => 'STRIPE-EMAIL-SEND',
            'reg_1' => 'EMAIL01',
        ])
        ->and(($retrievedMetadata['stripe_customer_id'] ?? null))
        ->toBe((string)($response->data()['customer'] ?? ''));

    $stored = api_test_runtime()->queryOne(
        'SELECT invoice_id, customer_id, url FROM stripe_module_orders WHERE id = ' . (int)$order['id']
    );

    expect($stored)
        ->not->toBeNull()
        ->and($stored['invoice_id'] ?? null)->toBe((string)$response->data()['id'])
        ->and($stored['customer_id'] ?? null)->not->toBe('')
        ->and($stored['url'] ?? null)->toBe((string)$response->data()['hosted_invoice_url']);
});

it('returns a conflict when a Stripe hosted invoice is already active for the order', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Stripe Email Guard Department',
    ]);
    $customer = api_fixtures()->createUser([
        'display_name' => 'Stripe Duplicate Guard Customer',
    ]);
    $order = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'reference' => 'STRIPE-EMAIL-GUARD',
        'reg_1' => 'EMAIL02',
    ]);
    $session = api_fixtures()->createUserSession([
        'modules_stripe_invoice_send',
    ]);
    $emailAddress = sprintf('stripe-duplicate-%d@example.com', (int)$order['id']);

    api_client()->post('/modules/stripe/invoice', [
        'email' => $emailAddress,
        'order_id' => $order['id'],
    ], $session['headers'])->assertStatus(200);

    $response = api_client()->post('/modules/stripe/invoice', [
        'email' => $emailAddress,
        'order_id' => $order['id'],
    ], $session['headers']);

    $response
        ->assertStatus(409)
        ->assertEnvelope()
        ->assertSuccess(false)
        ->assertMessage('A Stripe payment link is already active for this order.');

    expect($response->data())
        ->toBeArray()
        ->toHaveKey('code', 'stripe_invoice_exists');
});

it('allows sending a new Stripe hosted invoice when the existing association is already terminal', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Stripe Email Terminal Guard Department',
    ]);
    $customer = api_fixtures()->createUser([
        'display_name' => 'Stripe Terminal Guard Customer',
    ]);
    $order = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'reference' => 'STRIPE-EMAIL-TERMINAL',
        'reg_1' => 'EMAIL05',
    ]);
    $session = api_fixtures()->createUserSession([
        'modules_stripe_invoice_send',
    ]);
    $emailAddress = sprintf('stripe-terminal-%d@example.com', (int)$order['id']);

    $firstResponse = api_client()->post('/modules/stripe/invoice', [
        'email' => $emailAddress,
        'order_id' => $order['id'],
    ], $session['headers']);

    $firstInvoiceId = (string)($firstResponse->data()['id'] ?? '');
    stripe_fake_http_client::setInvoiceState($firstInvoiceId, [
        'status' => 'void',
        'paid' => false,
    ]);

    $secondResponse = api_client()->post('/modules/stripe/invoice', [
        'email' => $emailAddress,
        'order_id' => $order['id'],
    ], $session['headers']);

    $secondResponse
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess(true);

    expect((string)($secondResponse->data()['id'] ?? ''))
        ->not->toBe('')
        ->not->toBe($firstInvoiceId);
});

it('voids an unpaid Stripe hosted invoice and clears the local order association', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Stripe Email Cancel Department',
    ]);
    $customer = api_fixtures()->createUser([
        'display_name' => 'Stripe Cancel Customer',
    ]);
    $order = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'reference' => 'STRIPE-EMAIL-CANCEL',
        'reg_1' => 'EMAIL03',
    ]);
    $session = api_fixtures()->createUserSession([
        'modules_stripe_invoice_send',
    ]);
    $emailAddress = sprintf('stripe-cancel-%d@example.com', (int)$order['id']);

    $sendResponse = api_client()->post('/modules/stripe/invoice', [
        'email' => $emailAddress,
        'order_id' => $order['id'],
    ], $session['headers']);
    $invoiceId = (string)($sendResponse->data()['id'] ?? '');

    $response = api_client()->delete('/modules/stripe/invoice', [
        'order_id' => $order['id'],
    ], $session['headers']);

    $response
        ->assertStatus(200)
        ->assertEnvelope()
        ->assertSuccess(true);

    expect(api_test_runtime()->queryOne(
        'SELECT invoice_id FROM stripe_module_orders WHERE id = ' . (int)$order['id']
    ))->toBeNull();

    expect((new stripe())->invoice->retrieve($invoiceId)->status)->toBe('void');
});

it('refuses to cancel a paid Stripe hosted invoice', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Stripe Email Paid Department',
    ]);
    $customer = api_fixtures()->createUser([
        'display_name' => 'Stripe Paid Customer',
    ]);
    $order = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'reference' => 'STRIPE-EMAIL-PAID',
        'reg_1' => 'EMAIL04',
    ]);
    $session = api_fixtures()->createUserSession([
        'modules_stripe_invoice_send',
    ]);
    $emailAddress = sprintf('stripe-paid-%d@example.com', (int)$order['id']);

    $sendResponse = api_client()->post('/modules/stripe/invoice', [
        'email' => $emailAddress,
        'order_id' => $order['id'],
    ], $session['headers']);
    $invoiceId = (string)($sendResponse->data()['id'] ?? '');

    stripe_fake_http_client::setInvoiceState($invoiceId, [
        'status' => 'paid',
        'paid' => true,
        'amount_due' => 0,
        'amount_paid' => 1000,
    ]);

    $response = api_client()->delete('/modules/stripe/invoice', [
        'order_id' => $order['id'],
    ], $session['headers']);

    $response
        ->assertStatus(409)
        ->assertEnvelope()
        ->assertSuccess(false)
        ->assertMessage('A paid Stripe payment link cannot be cancelled.');

    expect(api_test_runtime()->queryOne(
        'SELECT invoice_id FROM stripe_module_orders WHERE id = ' . (int)$order['id']
    ))->not->toBeNull();
});

it('returns a setup required error when creating a payment intent for a department without terminal setup', function (): void {
    $department = api_fixtures()->createDepartment([
        'name' => 'Stripe Payment Intent Pending Department',
    ]);
    $customer = api_fixtures()->createUser([
        'display_name' => 'Stripe Payment Intent Customer',
    ]);
    $order = api_fixtures()->createOrder([
        'customer_id' => $customer['customer_number'],
        'department_id' => $department['id'],
        'reference' => 'STRIPE-SETUP-REQUIRED',
        'reg_1' => 'STRIPE01',
    ]);
    $session = api_fixtures()->createUserSession([
        'charge_order',
        'department_access_' . (int)$department['id'],
    ]);

    $response = api_client()->post('/orders/module/stripe/payment_intent', [
        'id' => $order['id'],
        'reader' => 'reader_pending_setup',
        'tax_percentage' => 25,
    ], $session['headers']);

    $response
        ->assertStatus(409)
        ->assertEnvelope()
        ->assertSuccess(false)
        ->assertMessage('Card payments are not ready for this department. Open Stripe setup and choose a terminal location.');

    expect($response->data())
        ->toBeArray()
        ->toHaveKey('code', 'stripe_terminal_setup_required');
});
