<?php

namespace Tests\Feature\Importing\Api;

use App\Models\Location;
use App\Models\User;
use Database\Factories\AssetFactory;
use Illuminate\Support\Str;
use Database\Factories\UserFactory;
use Database\Factories\ImportFactory;
use PHPUnit\Framework\Attributes\Test;
use Illuminate\Foundation\Testing\WithFaker;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Notification;
use Illuminate\Testing\TestResponse;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\Support\Importing\UsersImportFileBuilder as ImportFileBuilder;

class ImportUsersTest extends ImportDataTestCase
{
    use WithFaker;

    protected function importFileResponse(array $parameters = []): TestResponse
    {
        if (!array_key_exists('import-type', $parameters)) {
            $parameters['import-type'] = 'user';
        }

        return parent::importFileResponse($parameters);
    }

    #[Test]
    #[DataProvider('permissionsTestData')]
    public function onlyUserWithPermissionCanImportUsers(array|string $permissions): void
    {
        $permissions = collect((array) $permissions)
            ->map(fn (string $permission) => [$permission => '1'])
            ->toJson();

        $this->actingAsForApi(UserFactory::new()->create(['permissions' => $permissions]));

        $this->importFileResponse(['import' => 44])->assertForbidden();
    }

    #[Test]
    public function userWithImportAssetsPermissionCanImportUsers(): void
    {
        $this->actingAsForApi(UserFactory::new()->canImport()->create());

        $import = ImportFactory::new()->users()->create();

        $this->importFileResponse(['import' => $import->id])->assertOk();
    }

    #[Test]
    public function importUsers(): void
    {
        Notification::fake();

        $importFileBuilder = ImportFileBuilder::new();
        $row = $importFileBuilder->firstRow();
        $import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);

        $this->actingAsForApi(UserFactory::new()->superuser()->create());
        $this->importFileResponse(['import' => $import->id, 'send-welcome' => 1])
            ->assertOk()
            ->assertExactJson([
                'payload'  => null,
                'status'   => 'success',
                'messages' => ['redirect_url' => route('users.index')]
            ]);

        $newUser = User::query()
            ->with(['company', 'location'])
            ->where('username', $row['username'])
            ->sole();

        Notification::assertNothingSent();

        $this->assertEquals($newUser->email, $row['email']);
        $this->assertEquals($newUser->first_name, $row['firstName']);
        $this->assertEquals($newUser->last_name, $row['lastName']);
        $this->assertEquals($newUser->employee_num, $row['employeeNumber']);
        $this->assertEquals($newUser->company->name, $row['companyName']);
        $this->assertEquals($newUser->location->name, $row['location']);
        $this->assertEquals($newUser->phone, $row['phoneNumber']);
        $this->assertEquals($newUser->jobtitle, $row['position']);
        $this->assertTrue(Hash::isHashed($newUser->password));
        $this->assertEquals($newUser->website, '');
        $this->assertEquals($newUser->country, '');
        $this->assertEquals($newUser->address, '');
        $this->assertEquals($newUser->city, '');
        $this->assertEquals($newUser->state, '');
        $this->assertEquals($newUser->zip, '');
        $this->assertNull($newUser->permissions);
        $this->assertNull($newUser->avatar);
        $this->assertNull($newUser->notes);
        $this->assertNull($newUser->skin);
        $this->assertNull($newUser->department_id);
        $this->assertNull($newUser->two_factor_secret);
        $this->assertNull($newUser->idap_import);
        $this->assertEquals($newUser->locale, 'en-US');
        $this->assertEquals($newUser->show_in_list, 1);
        $this->assertEquals($newUser->two_factor_enrolled, 0);
        $this->assertEquals($newUser->two_factor_optin, 0);
        $this->assertEquals($newUser->remote, 0);
        $this->assertEquals($newUser->autoassign_licenses, 0);
        $this->assertEquals($newUser->vip, 0);
        $this->assertEquals($newUser->enable_sounds, 0);
        $this->assertEquals($newUser->enable_confetti, 0);
        $this->assertNull($newUser->created_by);
        $this->assertNull($newUser->start_date);
        $this->assertNull($newUser->end_date);
        $this->assertNull($newUser->scim_externalid);
        $this->assertNull($newUser->manager_id);
        $this->assertNull($newUser->activation_code);
        $this->assertNull($newUser->last_login);
        $this->assertNull($newUser->persist_code);
        $this->assertNull($newUser->reset_password_code);
        $this->assertEquals($newUser->activated, 0);
    }

    #[Test]
    public function willIgnoreUnknownColumnsWhenFileContainsUnknownColumns(): void
    {
        $row = ImportFileBuilder::new()->definition();
        $row['unknownColumnInCsvFile'] = 'foo';

        $importFileBuilder = new ImportFileBuilder([$row]);

        $this->actingAsForApi(UserFactory::new()->superuser()->create());

        $import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);

        $this->importFileResponse(['import' => $import->id])->assertOk();
    }

    #[Test]
    public function willNotCreateNewUserWhenUserWithUserNameAlreadyExist(): void
    {
        $user = UserFactory::new()->create(['username' => Str::random()]);
        $importFileBuilder = ImportFileBuilder::times(4)->replace(['username' => $user->username]);
        $import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);

        $this->actingAsForApi(UserFactory::new()->superuser()->create());
        $this->importFileResponse(['import' => $import->id])->assertOk();

        $probablyNewUsers = User::query()
            ->where('username', $user->username)
            ->get();

        $this->assertCount(1, $probablyNewUsers);
    }

    #[Test]
    public function willGenerateUsernameWhenUsernameFieldIsMissing(): void
    {
        $importFileBuilder = ImportFileBuilder::new()->forget('username');
        $row = $importFileBuilder->firstRow();
        $import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);

        $this->actingAsForApi(UserFactory::new()->superuser()->create());
        $this->importFileResponse(['import' => $import->id])->assertOk();

        $newUser = User::query()
            ->where('email', $row['email'])
            ->sole();

        $generatedUsername = User::generateFormattedNameFromFullName("{$row['firstName']} {$row['lastName']}")['username'];

        $this->assertEquals($newUser->username, $generatedUsername);
    }

    #[Test]
    public function willUpdateLocationOfAllAssetsAssignedToUser(): void
    {
        $user = UserFactory::new()->create(['username' => Str::random()]);
        $assetsAssignedToUser = AssetFactory::new()->create(['assigned_to' => $user->id, 'assigned_type' => User::class]);
        $importFileBuilder = ImportFileBuilder::new(['username' => $user->username]);
        $import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);

        $this->actingAsForApi(UserFactory::new()->superuser()->create());
        $this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk();

        $userLocation = Location::query()->where('name', $importFileBuilder->firstRow()['location'])->sole(['id']);

        $this->assertEquals(
            $assetsAssignedToUser->refresh()->location_id,
            $userLocation->id
        );
    }

    #[Test]
    public function whenRequiredColumnsAreMissingInImportFile(): void
    {
        $importFileBuilder = ImportFileBuilder::new(['firstName' => ''])->forget(['username']);
        $import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);

        $this->actingAsForApi(UserFactory::new()->superuser()->create());

        $this->importFileResponse(['import' => $import->id])
            ->assertInternalServerError()
            ->assertExactJson([
                'status'   => 'import-errors',
                'payload'  => null,
                'messages' => [
                    '' => [
                        'User' => [
                            'first_name' => ['The first name field is required.'],
                        ]
                    ]
                ]
            ]);

        $newUsers = User::query()
            ->where('email', $importFileBuilder->firstRow()['email'])
            ->get();

        $this->assertCount(0, $newUsers);
    }

    #[Test]
    public function updateUserFromImport(): void
    {
        $user = UserFactory::new()->create(['username' => Str::random()])->refresh();
        $importFileBuilder = ImportFileBuilder::new(['username'  => $user->username]);

        $row = $importFileBuilder->firstRow();
        $import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);

        $this->actingAsForApi(UserFactory::new()->superuser()->create());
        $this->importFileResponse(['import' => $import->id, 'import-update' => true])->assertOk();

        $updatedUser = User::query()->with(['company', 'location'])->find($user->id);
        $updatedAttributes = [
            'first_name', 'email', 'last_name', 'employee_num', 'company',
            'location_id', 'company_id', 'updated_at', 'phone', 'jobtitle'
        ];

        $this->assertEquals($updatedUser->email, $row['email']);
        $this->assertEquals($updatedUser->first_name, $row['firstName']);
        $this->assertEquals($updatedUser->last_name, $row['lastName']);
        $this->assertEquals($updatedUser->employee_num, $row['employeeNumber']);
        $this->assertEquals($updatedUser->company->name, $row['companyName']);
        $this->assertEquals($updatedUser->location->name, $row['location']);
        $this->assertEquals($updatedUser->phone, $row['phoneNumber']);
        $this->assertEquals($updatedUser->jobtitle, $row['position']);
        $this->assertTrue(Hash::isHashed($updatedUser->password));

        $this->assertEquals(
            Arr::except($updatedUser->attributesToArray(), $updatedAttributes),
            Arr::except($user->attributesToArray(), $updatedAttributes),
        );
    }

    #[Test]
    public function customColumnMapping(): void
    {
        $faker = ImportFileBuilder::new()->definition();
        $row = [
            'companyName'    => $faker['username'],
            'email'          => $faker['position'],
            'employeeNumber' => $faker['phoneNumber'],
            'firstName'       => $faker['location'],
            'lastName'       => $faker['lastName'],
            'location'       => $faker['firstName'],
            'phoneNumber'    => $faker['employeeNumber'],
            'position'       => $faker['email'],
            'username'       => $faker['companyName'],
        ];

        $importFileBuilder = new ImportFileBuilder([$row]);
        $import = ImportFactory::new()->users()->create(['file_path' => $importFileBuilder->saveToImportsDirectory()]);

        $this->actingAsForApi(UserFactory::new()->superuser()->create());

        $this->importFileResponse([
            'import' => $import->id,
            'column-mappings' => [
                'Company'         => 'username',
                'email'           => 'jobtitle',
                'Employee Number' => 'phone_number',
                'First Name'      => 'location',
                'Last Name'       => 'last_name',
                'Location'        => 'first_name',
                'Phone Number'    => 'employee_num',
                'Job Title'       => 'email',
                'Username'        => 'company',
            ]
        ])->assertOk();

        $newUser = User::query()
            ->with(['company', 'location'])
            ->where('username', $row['companyName'])
            ->sole();

        $this->assertEquals($newUser->email, $row['position']);
        $this->assertEquals($newUser->first_name, $row['location']);
        $this->assertEquals($newUser->last_name, $row['lastName']);
        $this->assertEquals($newUser->jobtitle, $row['email']);
        $this->assertEquals($newUser->employee_num, $row['phoneNumber']);
        $this->assertEquals($newUser->company->name, $row['username']);
        $this->assertEquals($newUser->location->name, $row['firstName']);
        $this->assertEquals($newUser->phone, $row['employeeNumber']);
        $this->assertTrue(Hash::isHashed($newUser->password));
        $this->assertEquals($newUser->website, '');
        $this->assertEquals($newUser->country, '');
        $this->assertEquals($newUser->address, '');
        $this->assertEquals($newUser->city, '');
        $this->assertEquals($newUser->state, '');
        $this->assertEquals($newUser->zip, '');
        $this->assertNull($newUser->permissions);
        $this->assertNull($newUser->avatar);
        $this->assertNull($newUser->notes);
        $this->assertNull($newUser->skin);
        $this->assertNull($newUser->department_id);
        $this->assertNull($newUser->two_factor_secret);
        $this->assertNull($newUser->idap_import);
        $this->assertEquals($newUser->locale, 'en-US');
        $this->assertEquals($newUser->show_in_list, 1);
        $this->assertEquals($newUser->two_factor_enrolled, 0);
        $this->assertEquals($newUser->two_factor_optin, 0);
        $this->assertEquals($newUser->remote, 0);
        $this->assertEquals($newUser->autoassign_licenses, 0);
        $this->assertEquals($newUser->vip, 0);
        $this->assertEquals($newUser->enable_sounds, 0);
        $this->assertEquals($newUser->enable_confetti, 0);
        $this->assertNull($newUser->created_by);
        $this->assertNull($newUser->start_date);
        $this->assertNull($newUser->end_date);
        $this->assertNull($newUser->scim_externalid);
        $this->assertNull($newUser->manager_id);
        $this->assertNull($newUser->activation_code);
        $this->assertNull($newUser->last_login);
        $this->assertNull($newUser->persist_code);
        $this->assertNull($newUser->reset_password_code);
        $this->assertEquals($newUser->activated, 0);
    }
}