<?php

declare(strict_types=1);

namespace Tests\Feature\Masters;

use App\Enums\AuditEvent;
use App\Models\AuditLog;
use App\Models\Location;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class LocationTest extends TestCase
{
    use RefreshDatabase;

    // -------------------------------------------------------------- access

    #[Test]
    public function a_guest_cannot_reach_locations(): void
    {
        $this->get(route('masters.locations.index'))->assertRedirect(route('login'));
    }

    #[Test]
    public function any_signed_in_user_can_manage_locations(): void
    {
        $user = User::factory()->create();

        $this->actingAs($user)->get(route('masters.locations.index'))->assertOk();
        $this->actingAs($user)->get(route('masters.locations.create'))->assertOk();

        $this->actingAs($user)
            ->post(route('masters.locations.store'), [
                'name' => 'Pune HQ',
                'code' => 'PNQ',
                'city' => 'Pune',
                'is_active' => '1',
            ])
            ->assertRedirect(route('masters.locations.index'));

        $this->assertDatabaseHas('locations', ['name' => 'Pune HQ', 'city' => 'Pune']);
    }

    #[Test]
    public function a_regular_user_cannot_delete_or_restore(): void
    {
        $user = User::factory()->create();
        $location = Location::factory()->create();

        $this->actingAs($user)
            ->delete(route('masters.locations.destroy', $location))
            ->assertForbidden();

        $location->delete();

        $this->actingAs($user)
            ->patch(route('masters.locations.restore', $location))
            ->assertForbidden();
    }

    // ------------------------------------------------------------- listing

    #[Test]
    public function the_listing_can_be_searched_by_name_code_or_city(): void
    {
        Location::factory()->create(['name' => 'Pune HQ', 'code' => 'PNQ', 'city' => 'Pune']);
        Location::factory()->create(['name' => 'Bangalore Office', 'code' => 'BLR', 'city' => 'Bangalore']);

        $user = User::factory()->create();

        foreach (['Pune HQ' => 'pune hq', 'Pune HQ ' => 'PNQ', 'Pune HQ  ' => 'Pune'] as $term) {
            $this->actingAs($user)
                ->get(route('masters.locations.index', ['search' => $term]))
                ->assertOk()
                ->assertSee('Pune HQ')
                ->assertDontSee('Bangalore Office');
        }
    }

    #[Test]
    public function the_listing_can_be_filtered_by_status(): void
    {
        Location::factory()->create(['name' => 'Pune HQ']);
        Location::factory()->inactive()->create(['name' => 'Old Annexe']);

        $this->actingAs(User::factory()->create())
            ->get(route('masters.locations.index', ['status' => 'inactive']))
            ->assertOk()
            ->assertSee('Old Annexe')
            ->assertDontSee('Pune HQ');
    }

    #[Test]
    public function the_listing_is_paginated(): void
    {
        Location::factory()->count(30)->create();

        $response = $this->actingAs(User::factory()->create())
            ->get(route('masters.locations.index'));

        $response->assertOk();
        $this->assertSame(25, $response->viewData('locations')->perPage());
        $this->assertSame(30, $response->viewData('locations')->total());
    }

    // -------------------------------------------------------------- create

    #[Test]
    public function the_name_is_required_and_unique(): void
    {
        Location::factory()->create(['name' => 'Pune HQ']);
        $user = User::factory()->create();

        $this->actingAs($user)
            ->post(route('masters.locations.store'), ['name' => '', 'is_active' => '1'])
            ->assertSessionHasErrors('name');

        $this->actingAs($user)
            ->post(route('masters.locations.store'), ['name' => 'Pune HQ', 'is_active' => '1'])
            ->assertSessionHasErrors('name');
    }

    #[Test]
    public function every_address_field_is_optional(): void
    {
        // "Remote — WFH" is a legitimate location with no street address.
        $this->actingAs(User::factory()->create())
            ->post(route('masters.locations.store'), [
                'name' => 'Remote — WFH',
                'is_active' => '1',
            ])
            ->assertSessionHasNoErrors();

        $location = Location::query()->where('name', 'Remote — WFH')->sole();

        $this->assertNull($location->city);
        $this->assertNull($location->address_line1);
        $this->assertSame('', $location->fullAddress());
    }

    #[Test]
    public function blank_address_fields_are_stored_as_null_not_empty_strings(): void
    {
        $this->actingAs(User::factory()->create())->post(route('masters.locations.store'), [
            'name' => 'Pune HQ',
            'address_line1' => '   ',
            'city' => '',
            'is_active' => '1',
        ]);

        $location = Location::query()->where('name', 'Pune HQ')->sole();

        $this->assertNull($location->address_line1);
        $this->assertNull($location->city);
    }

    #[Test]
    public function the_full_address_joins_the_parts_that_are_present(): void
    {
        $location = Location::factory()->create([
            'address_line1' => 'Baner Road',
            'address_line2' => null,
            'city' => 'Pune',
            'state' => 'Maharashtra',
            'postal_code' => '411045',
            'country' => 'India',
        ]);

        $this->assertSame(
            'Baner Road, Pune, Maharashtra, 411045, India',
            $location->fullAddress(),
        );
    }

    #[Test]
    public function the_code_is_upper_cased_and_must_be_unique(): void
    {
        Location::factory()->create(['code' => 'PNQ']);
        $user = User::factory()->create();

        $this->actingAs($user)->post(route('masters.locations.store'), [
            'name' => 'Bangalore Office',
            'code' => 'blr',
            'is_active' => '1',
        ])->assertSessionHasNoErrors();

        $this->assertSame('BLR', Location::query()->where('name', 'Bangalore Office')->sole()->code);

        $this->actingAs($user)
            ->post(route('masters.locations.store'), ['name' => 'Another', 'code' => 'PNQ', 'is_active' => '1'])
            ->assertSessionHasErrors('code');
    }

    #[Test]
    public function creating_a_location_is_audited(): void
    {
        $user = User::factory()->create();
        AuditLog::query()->delete();

        $this->actingAs($user)->post(route('masters.locations.store'), [
            'name' => 'Pune HQ',
            'is_active' => '1',
        ]);

        $log = AuditLog::query()->sole();

        $this->assertSame(AuditEvent::Created, $log->event);
        $this->assertSame('Created Location Pune HQ', $log->description);
    }

    // -------------------------------------------------------------- update

    #[Test]
    public function a_location_can_be_updated(): void
    {
        $location = Location::factory()->create(['name' => 'Pune HQ', 'city' => 'Pune']);

        $this->actingAs(User::factory()->create())
            ->put(route('masters.locations.update', $location), [
                'name' => 'Pune Head Office',
                'code' => 'PNQ',
                'address_line1' => 'Baner Road',
                'city' => 'Pune',
                'state' => 'Maharashtra',
                'country' => 'India',
                'postal_code' => '411045',
                'is_active' => '1',
            ])
            ->assertRedirect(route('masters.locations.index'));

        $location->refresh();

        $this->assertSame('Pune Head Office', $location->name);
        $this->assertSame('Baner Road', $location->address_line1);
    }

    #[Test]
    public function updating_a_location_is_audited_with_its_diff(): void
    {
        $user = User::factory()->create();
        $location = Location::factory()->create(['name' => 'Pune HQ']);
        AuditLog::query()->delete();

        $this->actingAs($user)->put(route('masters.locations.update', $location), [
            'name' => 'Pune Head Office',
            'code' => $location->code,
            'address_line1' => $location->address_line1,
            'city' => $location->city,
            'state' => $location->state,
            'country' => $location->country,
            'postal_code' => $location->postal_code,
            'is_active' => '1',
        ]);

        $log = AuditLog::query()->sole();

        $this->assertSame(AuditEvent::Updated, $log->event);
        $this->assertSame(['name' => 'Pune HQ'], $log->old_values);
        $this->assertSame(['name' => 'Pune Head Office'], $log->new_values);
    }

    // ------------------------------------------------------ delete/restore

    #[Test]
    public function a_master_can_delete_and_restore_a_location(): void
    {
        $location = Location::factory()->create(['name' => 'Pune HQ']);
        $master = User::factory()->master()->create();

        $this->actingAs($master)
            ->delete(route('masters.locations.destroy', $location))
            ->assertRedirect(route('masters.locations.index'));

        $this->assertSoftDeleted('locations', ['id' => $location->getKey()]);

        $this->actingAs($master)
            ->patch(route('masters.locations.restore', $location))
            ->assertRedirect(route('masters.locations.index'));

        $this->assertNotSoftDeleted('locations', ['id' => $location->getKey()]);
    }

    #[Test]
    public function deleting_and_restoring_are_audited(): void
    {
        $location = Location::factory()->create(['name' => 'Pune HQ']);
        $master = User::factory()->master()->create();

        AuditLog::query()->delete();
        $this->actingAs($master)->delete(route('masters.locations.destroy', $location));
        $this->assertSame(AuditEvent::Deleted, AuditLog::query()->sole()->event);

        AuditLog::query()->delete();
        $this->actingAs($master)->patch(route('masters.locations.restore', $location));
        $this->assertSame(AuditEvent::Restored, AuditLog::query()->sole()->event);
    }

    #[Test]
    public function deleted_locations_appear_only_in_the_deleted_listing(): void
    {
        $location = Location::factory()->create(['name' => 'Pune HQ']);
        $location->delete();

        $master = User::factory()->master()->create();

        $this->actingAs($master)
            ->get(route('masters.locations.index'))
            ->assertOk()
            ->assertDontSee('Pune HQ');

        $this->actingAs($master)
            ->get(route('masters.locations.index', ['trashed' => 1]))
            ->assertOk()
            ->assertSee('Pune HQ');
    }
}
