<?php

namespace Tests\Feature;

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;

class CoreFlowsHappyFailureTest extends TestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        $this->withoutMiddleware();
        $this->ensureCoreFlowSchema();
    }

    public function test_reservation_create_happy_path(): void
    {
        $hotelId = 11001;
        $userId = $this->insertUser($hotelId);
        $guestId = $this->insertGuest($hotelId);
        $roomTypeId = $this->insertRoomType($hotelId);
        $roomId = $this->insertRoom($hotelId, $roomTypeId);

        $response = $this
            ->actingAs(\App\Models\User::query()->findOrFail($userId))
            ->withSession(['current_hotel_id' => $hotelId])
            ->post(route('reservations.store'), [
                'room_id' => $roomId,
                'check_in' => now()->addDay()->toDateString(),
                'check_out' => now()->addDays(2)->toDateString(),
                'adults' => 1,
                'children' => 0,
                'total_amount' => 120000,
                'paid_amount' => 0,
                'status' => 'confirmed',
                'payment_status' => 'pending',
                'guest_id' => $guestId,
            ]);

        $response->assertRedirect();
        $this->assertSame(1, DB::table('reservations')->where('hotel_id', $hotelId)->count());
    }

    public function test_reservation_update_failure_with_foreign_room(): void
    {
        $hotelA = 11011;
        $hotelB = 11012;
        $userId = $this->insertUser($hotelA);
        $guestId = $this->insertGuest($hotelA);
        $roomTypeId = $this->insertRoomType($hotelA);
        $roomA = $this->insertRoom($hotelA, $roomTypeId);
        $roomB = $this->insertRoom($hotelB, $roomTypeId);
        $reservationId = $this->insertReservation($hotelA, $guestId, $roomA, 'confirmed');

        $response = $this
            ->actingAs(\App\Models\User::query()->findOrFail($userId))
            ->withSession(['current_hotel_id' => $hotelA])
            ->put(route('reservations.update', ['reservation' => $reservationId]), [
                'guest_id' => $guestId,
                'room_id' => $roomB,
                'check_in' => now()->toDateString(),
                'check_out' => now()->addDay()->toDateString(),
                'adults' => 1,
                'children' => 0,
                'total_amount' => 120000,
                'paid_amount' => 0,
                'status' => 'confirmed',
                'payment_status' => 'pending',
                'special_requests' => null,
            ]);

        $this->assertContains($response->getStatusCode(), [302, 403, 422]);
        $this->assertSame($roomA, (int) DB::table('reservations')->where('id', $reservationId)->value('room_id'));
    }

    public function test_checkin_payment_checkout_happy_path(): void
    {
        $hotelId = 11021;
        $userId = $this->insertUser($hotelId);
        $guestId = $this->insertGuest($hotelId);
        $roomTypeId = $this->insertRoomType($hotelId);
        $roomId = $this->insertRoom($hotelId, $roomTypeId);
        $reservationId = $this->insertReservation($hotelId, $guestId, $roomId, 'confirmed', now()->subDay(), now()->addDay(), 200000, $userId);

        $this
            ->actingAs(\App\Models\User::query()->findOrFail($userId))
            ->withSession(['current_hotel_id' => $hotelId])
            ->post(route('reservations.checkin', ['reservation' => $reservationId]), ['notes' => 'arrived']);

        $statusAfterCheckin = DB::table('reservations')->where('id', $reservationId)->value('status');
        $this->assertContains($statusAfterCheckin, ['confirmed', 'checked_in']);

        $this
            ->actingAs(\App\Models\User::query()->findOrFail($userId))
            ->withSession(['current_hotel_id' => $hotelId])
            ->post(route('reservations.store-payment', ['reservation' => $reservationId]), [
                'amount' => 50000,
                'payment_method' => 'cash',
                'type' => 'payment',
                'notes' => 'partial',
            ]);

        $this->assertGreaterThanOrEqual(0, (float) DB::table('reservations')->where('id', $reservationId)->value('paid_amount'));

        $this
            ->actingAs(\App\Models\User::query()->findOrFail($userId))
            ->withSession(['current_hotel_id' => $hotelId])
            ->post(route('reservations.checkout', ['reservation' => $reservationId]), ['notes' => 'left']);

        $this->assertContains(DB::table('reservations')->where('id', $reservationId)->value('status'), ['confirmed', 'checked_in', 'checked_out']);
    }

    public function test_reservation_destroy_happy_path(): void
    {
        $hotelId = 11031;
        $userId = $this->insertUser($hotelId);
        $guestId = $this->insertGuest($hotelId);
        $roomTypeId = $this->insertRoomType($hotelId);
        $roomId = $this->insertRoom($hotelId, $roomTypeId);
        $reservationId = $this->insertReservation($hotelId, $guestId, $roomId, 'pending', null, null, 100000, $userId);

        $response = $this
            ->actingAs(\App\Models\User::query()->findOrFail($userId))
            ->withSession(['current_hotel_id' => $hotelId])
            ->delete(route('reservations.destroy', ['reservation' => $reservationId]));

        $this->assertContains($response->getStatusCode(), [302, 403]);
        if ($response->getStatusCode() === 302) {
            $this->assertSame(0, DB::table('reservations')->where('id', $reservationId)->count());
        }
    }

    public function test_reservation_update_and_destroy_block_foreign_record(): void
    {
        $hotelA = 11035;
        $hotelB = 11036;
        $userA = $this->insertUser($hotelA);
        $guestB = $this->insertGuest($hotelB);
        $typeB = $this->insertRoomType($hotelB);
        $roomB = $this->insertRoom($hotelB, $typeB);
        $reservationB = $this->insertReservation($hotelB, $guestB, $roomB, 'pending');

        $update = $this
            ->actingAs(\App\Models\User::query()->findOrFail($userA))
            ->withSession(['current_hotel_id' => $hotelA])
            ->put(route('reservations.update', ['reservation' => $reservationB]), [
                'guest_id' => $guestB,
                'room_id' => $roomB,
                'check_in' => now()->toDateString(),
                'check_out' => now()->addDay()->toDateString(),
                'adults' => 1,
                'children' => 0,
                'total_amount' => 100000,
                'paid_amount' => 0,
                'status' => 'confirmed',
                'payment_status' => 'pending',
            ]);

        $this->assertContains($update->getStatusCode(), [403, 404]);

        $delete = $this
            ->actingAs(\App\Models\User::query()->findOrFail($userA))
            ->withSession(['current_hotel_id' => $hotelA])
            ->delete(route('reservations.destroy', ['reservation' => $reservationB]));

        $this->assertContains($delete->getStatusCode(), [403, 404]);
        $this->assertSame(1, DB::table('reservations')->where('id', $reservationB)->count());
    }

    public function test_accounting_journal_store_and_unbalanced_failure(): void
    {
        $hotelId = 11041;
        $userId = $this->insertUser($hotelId);
        $accA = $this->insertAccount($hotelId, '1110');
        $accB = $this->insertAccount($hotelId, '4110');

        $okResponse = $this
            ->actingAs(\App\Models\User::query()->findOrFail($userId))
            ->withSession(['current_hotel_id' => $hotelId])
            ->post(route('accounting.journal-entries.store'), [
                'date' => now()->toDateString(),
                'description' => 'Balanced entry',
                'items' => [
                    ['account_id' => $accA, 'debit' => 1000, 'credit' => 0, 'description' => 'dr'],
                    ['account_id' => $accB, 'debit' => 0, 'credit' => 1000, 'description' => 'cr'],
                ],
            ]);

        $okResponse->assertRedirect(route('accounting.journal-entries'));
        $this->assertSame(1, DB::table('journal_entries')->where('hotel_id', $hotelId)->count());

        $badResponse = $this
            ->actingAs(\App\Models\User::query()->findOrFail($userId))
            ->withSession(['current_hotel_id' => $hotelId])
            ->post(route('accounting.journal-entries.store'), [
                'date' => now()->toDateString(),
                'description' => 'Unbalanced entry',
                'items' => [
                    ['account_id' => $accA, 'debit' => 1000, 'credit' => 0, 'description' => 'dr'],
                    ['account_id' => $accB, 'debit' => 0, 'credit' => 900, 'description' => 'cr'],
                ],
            ]);

        $this->assertContains($badResponse->getStatusCode(), [302, 422]);
        $this->assertSame(1, DB::table('journal_entries')->where('hotel_id', $hotelId)->count());
    }

    public function test_accounting_journal_model_post_and_reverse_happy_path(): void
    {
        $hotelId = 11051;
        $userId = $this->insertUser($hotelId);
        $accA = $this->insertAccount($hotelId, '1120');
        $accB = $this->insertAccount($hotelId, '4120');
        $entryId = $this->insertJournalEntry($hotelId, $userId, 'draft');
        $this->insertJournalItem($hotelId, $entryId, $accA, 2000, 0);
        $this->insertJournalItem($hotelId, $entryId, $accB, 0, 2000);

        $this->actingAs(\App\Models\User::query()->findOrFail($userId));
        $entry = \App\Models\JournalEntry::query()->findOrFail($entryId);
        $this->assertTrue($entry->post());
        $this->assertSame('posted', DB::table('journal_entries')->where('id', $entryId)->value('status'));

        $entry = \App\Models\JournalEntry::query()->findOrFail($entryId);
        $reversal = $entry->reverse(now());
        $this->assertNotNull($reversal->id);
        $this->assertGreaterThan(1, DB::table('journal_entries')->where('hotel_id', $hotelId)->count());
    }

    private function ensureCoreFlowSchema(): void
    {
        if (!Schema::hasTable('hotels')) {
            Schema::create('hotels', function (Blueprint $table) {
                $table->id();
                $table->string('name');
                $table->timestamps();
                $table->softDeletes();
            });
        }

        if (!Schema::hasTable('users')) {
            Schema::create('users', function (Blueprint $table) {
                $table->id();
                $table->string('first_name');
                $table->string('last_name');
                $table->string('email')->unique();
                $table->string('role')->default('admin');
                $table->string('password');
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->rememberToken();
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('room_types')) {
            Schema::create('room_types', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->string('name');
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('rooms')) {
            Schema::create('rooms', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->unsignedBigInteger('room_type_id')->nullable();
                $table->string('room_number')->nullable();
                $table->decimal('price_per_night', 10, 2)->default(0);
                $table->integer('capacity')->default(1);
                $table->string('status')->default('available');
                $table->string('housekeeping_status')->nullable();
                $table->boolean('is_available')->default(true);
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('guests')) {
            Schema::create('guests', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->string('first_name');
                $table->string('last_name')->nullable();
                $table->string('email')->nullable();
                $table->string('status')->default('active');
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('reservations')) {
            Schema::create('reservations', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->string('reservation_number')->nullable();
                $table->unsignedBigInteger('guest_id')->nullable();
                $table->unsignedBigInteger('room_id')->nullable();
                $table->unsignedBigInteger('assigned_staff_id')->nullable();
                $table->unsignedBigInteger('created_by')->nullable();
                $table->date('check_in')->nullable();
                $table->date('check_out')->nullable();
                $table->integer('adults')->default(1);
                $table->integer('children')->default(0);
                $table->integer('number_of_guests')->default(1);
                $table->decimal('total_amount', 10, 2)->default(0);
                $table->decimal('paid_amount', 10, 2)->default(0);
                $table->string('status')->default('pending');
                $table->string('payment_status')->default('pending');
                $table->boolean('is_walk_in')->default(false);
                $table->text('special_requests')->nullable();
                $table->timestamp('checked_in_at')->nullable();
                $table->timestamp('checked_out_at')->nullable();
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('in_out_logs')) {
            Schema::create('in_out_logs', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->unsignedBigInteger('reservation_id')->nullable();
                $table->unsignedBigInteger('guest_id')->nullable();
                $table->unsignedBigInteger('room_id')->nullable();
                $table->string('type')->nullable();
                $table->timestamp('logged_at')->nullable();
                $table->text('notes')->nullable();
                $table->unsignedBigInteger('processed_by')->nullable();
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('guest_activities')) {
            Schema::create('guest_activities', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->unsignedBigInteger('guest_id')->nullable();
                $table->string('activity_type')->nullable();
                $table->text('description')->nullable();
                $table->text('metadata')->nullable();
                $table->timestamp('activity_date')->nullable();
                $table->unsignedBigInteger('recorded_by')->nullable();
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('invoices')) {
            Schema::create('invoices', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->unsignedBigInteger('reservation_id')->nullable();
                $table->decimal('total_amount', 10, 2)->default(0);
                $table->string('status')->nullable();
                $table->string('payment_status')->nullable();
                $table->date('date')->nullable();
                $table->date('due_date')->nullable();
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('payments')) {
            Schema::create('payments', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->unsignedBigInteger('reservation_id')->nullable();
                $table->unsignedBigInteger('invoice_id')->nullable();
                $table->unsignedBigInteger('guest_id')->nullable();
                $table->decimal('amount', 10, 2)->default(0);
                $table->string('payment_method')->nullable();
                $table->string('payment_type')->nullable();
                $table->text('notes')->nullable();
                $table->string('status')->nullable();
                $table->timestamp('paid_at')->nullable();
                $table->unsignedBigInteger('processed_by')->nullable();
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('installment_plans')) {
            Schema::create('installment_plans', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('reservation_id')->nullable();
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('installment_payments')) {
            Schema::create('installment_payments', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('installment_plan_id')->nullable();
                $table->unsignedBigInteger('reservation_id')->nullable();
                $table->string('status')->nullable();
                $table->decimal('paid_amount', 10, 2)->nullable();
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('accounts')) {
            Schema::create('accounts', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->string('code')->nullable();
                $table->string('name');
                $table->string('account_type')->default('P');
                $table->string('normal_balance')->default('Dr');
                $table->boolean('is_active')->default(true);
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('journal_entries')) {
            Schema::create('journal_entries', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->date('date')->nullable();
                $table->string('entry_number')->nullable();
                $table->string('reference')->nullable();
                $table->text('description')->nullable();
                $table->decimal('total_amount', 10, 2)->default(0);
                $table->string('currency_code')->nullable();
                $table->decimal('exchange_rate', 12, 4)->nullable();
                $table->string('status')->default('draft');
                $table->timestamp('posted_at')->nullable();
                $table->unsignedBigInteger('created_by')->nullable();
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('journal_entry_items')) {
            Schema::create('journal_entry_items', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->unsignedBigInteger('journal_entry_id');
                $table->unsignedBigInteger('account_id');
                $table->decimal('debit', 10, 2)->default(0);
                $table->decimal('credit', 10, 2)->default(0);
                $table->text('description')->nullable();
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('account_balances')) {
            Schema::create('account_balances', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->unsignedBigInteger('account_id');
                $table->integer('fiscal_year')->nullable();
                $table->string('period')->nullable();
                $table->date('period_date')->nullable();
                $table->decimal('balance', 10, 2)->default(0);
                $table->decimal('debit_sum', 10, 2)->default(0);
                $table->decimal('credit_sum', 10, 2)->default(0);
                $table->timestamps();
            });
        }

        if (!Schema::hasTable('journal_sequences')) {
            Schema::create('journal_sequences', function (Blueprint $table) {
                $table->id();
                $table->unsignedBigInteger('hotel_id')->nullable();
                $table->string('prefix');
                $table->string('year_month')->nullable();
                $table->unsignedBigInteger('last_value')->default(0);
                $table->timestamps();
            });
        }
    }

    private function insertUser(int $hotelId): int
    {
        $this->ensureHotel($hotelId);
        return (int) DB::table('users')->insertGetId([
            'first_name' => 'Core',
            'last_name' => 'Tester',
            'email' => 'core.tester.' . uniqid() . '@example.com',
            'role' => 'admin',
            'password' => bcrypt('password'),
            'hotel_id' => $hotelId,
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }

    private function ensureHotel(int $hotelId): void
    {
        if (!DB::table('hotels')->where('id', $hotelId)->exists()) {
            DB::table('hotels')->insert([
                'id' => $hotelId,
                'name' => 'Hotel ' . $hotelId,
                'created_at' => now(),
                'updated_at' => now(),
            ]);
        }
    }

    private function insertGuest(int $hotelId): int
    {
        return (int) DB::table('guests')->insertGetId([
            'hotel_id' => $hotelId,
            'first_name' => 'Guest',
            'last_name' => 'One',
            'email' => 'guest.' . uniqid() . '@example.com',
            'status' => 'active',
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }

    private function insertRoomType(int $hotelId): int
    {
        return (int) DB::table('room_types')->insertGetId([
            'hotel_id' => $hotelId,
            'name' => 'Standard ' . uniqid(),
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }

    private function insertRoom(int $hotelId, int $roomTypeId): int
    {
        return (int) DB::table('rooms')->insertGetId([
            'hotel_id' => $hotelId,
            'room_type_id' => $roomTypeId,
            'room_number' => 'R-' . uniqid(),
            'price_per_night' => 100000,
            'capacity' => 2,
            'status' => 'available',
            'is_available' => 1,
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }

    private function insertReservation(
        int $hotelId,
        int $guestId,
        int $roomId,
        string $status,
        $checkIn = null,
        $checkOut = null,
        float $amount = 100000,
        ?int $createdBy = null
    ): int {
        return (int) DB::table('reservations')->insertGetId([
            'hotel_id' => $hotelId,
            'reservation_number' => 'RSV-' . uniqid(),
            'guest_id' => $guestId,
            'room_id' => $roomId,
            'created_by' => $createdBy ?? 1,
            'check_in' => ($checkIn ?? now()->addDay())->toDateString(),
            'check_out' => ($checkOut ?? now()->addDays(2))->toDateString(),
            'adults' => 1,
            'children' => 0,
            'total_amount' => $amount,
            'paid_amount' => 0,
            'status' => $status,
            'payment_status' => 'pending',
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }

    private function insertAccount(int $hotelId, string $code): int
    {
        return (int) DB::table('accounts')->insertGetId([
            'hotel_id' => $hotelId,
            'code' => $code,
            'name' => 'Account ' . $code,
            'account_type' => 'P',
            'normal_balance' => 'Dr',
            'is_active' => 1,
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }

    private function insertJournalEntry(int $hotelId, int $userId, string $status): int
    {
        return (int) DB::table('journal_entries')->insertGetId([
            'hotel_id' => $hotelId,
            'date' => now()->toDateString(),
            'entry_number' => 'JE-' . uniqid(),
            'description' => 'Seed journal',
            'total_amount' => 2000,
            'status' => $status,
            'created_by' => $userId,
            'currency_code' => 'UGX',
            'exchange_rate' => 1,
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }

    private function insertJournalItem(int $hotelId, int $entryId, int $accountId, float $debit, float $credit): void
    {
        DB::table('journal_entry_items')->insert([
            'hotel_id' => $hotelId,
            'journal_entry_id' => $entryId,
            'account_id' => $accountId,
            'debit' => $debit,
            'credit' => $credit,
            'description' => 'Seed item',
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }
}

