<?php

namespace Tests\Feature;

use App\Models\Company;
use App\Models\Department;
use App\Models\EmployeeImport;
use App\Models\EmployeeImportRow;
use App\Models\Role;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Tests\TestCase;

class EmployeeImportTest extends TestCase
{
    use RefreshDatabase;

    public function test_sync_import_creates_employees_and_records_results(): void
    {
        $this->withoutMiddleware();
        Storage::fake('public');
        [$actor] = $this->createActor();

        DB::table('employee_number_sequences')->insert([
            'company_id' => $actor->company_id,
            'prefix' => 'EMP',
            'digits' => 5,
            'last_number' => 7,
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        $csv = $this->makeCsv([
            ['name', 'email', 'password', 'employee_number'],
            ['Import One', 'import-one@demo.local', 'password123', ''],
        ]);

        $response = $this->actingAs($actor)->post(route('employees.import.store'), [
            'file' => UploadedFile::fake()->createWithContent('employees.csv', $csv),
        ]);

        $response->assertSessionHasNoErrors();
        $response->assertRedirect();
        $this->assertDatabaseHas('users', [
            'email' => 'import-one@demo.local',
            'employee_number' => 'EMP00008',
            'company_id' => $actor->company_id,
            'user_type' => User::USER_TYPE_STAFF,
            'status' => 'active',
            'allow_login' => 1,
            'is_superadmin' => 0,
        ]);

        $imported = User::query()->where('email', 'import-one@demo.local')->firstOrFail();
        $this->assertNotNull($imported->role_id);
        $this->assertTrue(Hash::check('password123', $imported->password));
        $this->assertTrue(
            User::query()
                ->forTenantDirectory()
                ->where('company_id', $actor->company_id)
                ->where('email', 'import-one@demo.local')
                ->exists()
        );

        $this->assertDatabaseHas('employee_imports', [
            'company_id' => $actor->company_id,
            'mode' => 'sync',
            'status' => 'completed',
            'success_rows' => 1,
            'failed_rows' => 0,
        ]);
    }

    public function test_import_accepts_title_case_gender_and_stores_lowercase(): void
    {
        $this->withoutMiddleware();
        Storage::fake('public');
        [$actor] = $this->createActor();

        $maleEmail = 'gender-male-' . Str::uuid() . '@demo.local';
        $femaleEmail = 'gender-female-' . Str::uuid() . '@demo.local';
        $csv = $this->makeCsv([
            ['name', 'email', 'password', 'gender'],
            ['Male User', $maleEmail, 'password123', 'Male'],
            ['Female User', $femaleEmail, 'password123', 'Female'],
        ]);

        $this->actingAs($actor)->post(route('employees.import.store'), [
            'file' => UploadedFile::fake()->createWithContent('genders.csv', $csv),
        ])->assertSessionHasNoErrors();

        $this->assertDatabaseHas('employee_imports', [
            'company_id' => $actor->company_id,
            'status' => 'completed',
            'success_rows' => 2,
            'failed_rows' => 0,
        ]);
        $this->assertDatabaseHas('users', [
            'email' => $maleEmail,
            'gender' => 'male',
            'company_id' => $actor->company_id,
        ]);
        $this->assertDatabaseHas('users', [
            'email' => $femaleEmail,
            'gender' => 'female',
            'company_id' => $actor->company_id,
        ]);
    }

    public function test_large_file_uses_queued_mode_and_processes_after_response(): void
    {
        $this->withoutMiddleware();
        Storage::fake('public');
        [$actor] = $this->createActor();

        $rows = [['name', 'email', 'password', 'employee_number']];
        for ($i = 1; $i <= 55; $i++) {
            $rows[] = ['Employee ' . $i, 'bulk-' . $i . '@demo.local', 'password123', ''];
        }

        $response = $this->actingAs($actor)->post(route('employees.import.store'), [
            'file' => UploadedFile::fake()->createWithContent('large.csv', $this->makeCsv($rows)),
        ]);

        $response->assertSessionHasNoErrors();
        $import = EmployeeImport::query()->latest()->firstOrFail();
        $this->assertSame('queued', $import->mode);
        $this->assertSame('completed', $import->status);
        $this->assertSame(55, (int) $import->processed_rows);
    }

    public function test_row_validation_failure_is_stored_without_breaking_import(): void
    {
        $this->withoutMiddleware();
        Storage::fake('public');
        [$actor] = $this->createActor();

        $csv = $this->makeCsv([
            ['name', 'email', 'password', 'employee_number'],
            ['', 'bad@demo.local', 'short', ''],
        ]);

        $this->actingAs($actor)->post(route('employees.import.store'), [
            'file' => UploadedFile::fake()->createWithContent('invalid.csv', $csv),
        ])->assertSessionHasNoErrors();

        $this->assertDatabaseHas('employee_imports', [
            'company_id' => $actor->company_id,
            'status' => 'completed',
            'success_rows' => 0,
            'failed_rows' => 1,
        ]);
        $this->assertDatabaseHas('employee_import_rows', [
            'status' => 'failed',
        ]);
        $this->assertDatabaseMissing('users', [
            'email' => 'bad@demo.local',
        ]);
    }

    public function test_department_name_creates_row_and_sets_user_department_id(): void
    {
        $this->withoutMiddleware();
        Storage::fake('public');
        [$actor] = $this->createActor();

        $email = 'dept-import-' . Str::uuid() . '@demo.local';
        $csv = $this->makeCsv([
            ['name', 'email', 'password', 'employee_number', 'department'],
            [
                'Dept Import User',
                $email,
                'password123',
                '',
                'Brand New Department Alpha',
            ],
        ]);

        $this->actingAs($actor)->post(route('employees.import.store'), [
            'file' => UploadedFile::fake()->createWithContent('employees.csv', $csv),
        ])->assertSessionHasNoErrors();

        $dept = Department::withoutGlobalScopes()
            ->where('company_id', $actor->company_id)
            ->whereRaw('LOWER(name) = ?', [mb_strtolower('Brand New Department Alpha', 'UTF-8')])
            ->first();
        $this->assertNotNull($dept);

        $this->assertDatabaseHas('users', [
            'email' => $email,
            'department_id' => $dept->id,
        ]);
    }

    public function test_department_reuses_existing_case_insensitively_no_duplicate(): void
    {
        $this->withoutMiddleware();
        Storage::fake('public');
        [$actor] = $this->createActor();

        $existing = Department::withoutGlobalScopes()->create([
            'company_id' => $actor->company_id,
            'name' => 'Engineering',
        ]);

        $email = 'case-' . Str::uuid() . '@demo.local';
        $csv = $this->makeCsv([
            ['name', 'email', 'password', 'employee_number', 'department'],
            ['Case User', $email, 'password123', '', '  eNgInEeRiNg  '],
        ]);

        $this->actingAs($actor)->post(route('employees.import.store'), [
            'file' => UploadedFile::fake()->createWithContent('employees.csv', $csv),
        ])->assertSessionHasNoErrors();

        $this->assertSame(1, Department::withoutGlobalScopes()
            ->where('company_id', $actor->company_id)
            ->whereRaw('LOWER(name) = ?', [mb_strtolower('Engineering', 'UTF-8')])
            ->count());
        $this->assertDatabaseHas('users', [
            'email' => $email,
            'department_id' => $existing->id,
        ]);
    }

    public function test_shift_name_not_found_fails_row_with_message(): void
    {
        $this->withoutMiddleware();
        Storage::fake('public');
        [$actor] = $this->createActor();

        $email = 'shift-fail-' . Str::uuid() . '@demo.local';
        $csv = $this->makeCsv([
            ['name', 'email', 'password', 'shift'],
            ['Shift Fail User', $email, 'password123', 'NonexistentShift999'],
        ]);

        $this->actingAs($actor)->post(route('employees.import.store'), [
            'file' => UploadedFile::fake()->createWithContent('employees.csv', $csv),
        ])->assertSessionHasNoErrors();

        $this->assertDatabaseHas('employee_import_rows', [
            'status' => 'failed',
        ]);
        $row = EmployeeImportRow::query()->latest('id')->first();
        $this->assertStringContainsString('NonexistentShift999', (string) $row->error_message);
        $this->assertStringContainsString('shift', mb_strtolower((string) $row->error_message));
        $this->assertDatabaseMissing('users', [
            'email' => $email,
        ]);
    }

    public function test_salary_group_not_found_fails_row(): void
    {
        $this->withoutMiddleware();
        Storage::fake('public');
        [$actor] = $this->createActor();

        $email = 'sg-fail-' . Str::uuid() . '@demo.local';
        $csv = $this->makeCsv([
            ['name', 'email', 'password', 'salary_group'],
            ['SG User', $email, 'password123', 'MissingSalaryGroupZZ'],
        ]);

        $this->actingAs($actor)->post(route('employees.import.store'), [
            'file' => UploadedFile::fake()->createWithContent('employees.csv', $csv),
        ])->assertSessionHasNoErrors();

        $this->assertDatabaseMissing('users', ['email' => $email]);
    }

    public function test_role_name_not_found_fails_row(): void
    {
        $this->withoutMiddleware();
        Storage::fake('public');
        [$actor] = $this->createActor();

        $email = 'role-fail-' . Str::uuid() . '@demo.local';
        $csv = $this->makeCsv([
            ['name', 'email', 'password', 'role'],
            ['Role User', $email, 'password123', 'ZebraRoleName'],
        ]);

        $this->actingAs($actor)->post(route('employees.import.store'), [
            'file' => UploadedFile::fake()->createWithContent('employees.csv', $csv),
        ])->assertSessionHasNoErrors();

        $this->assertDatabaseMissing('users', ['email' => $email]);
        $row = EmployeeImportRow::query()->where('error_message', 'like', '%ZebraRoleName%')->first();
        $this->assertNotNull($row);
    }

    public function test_download_template_sample_aligns_with_headers(): void
    {
        $this->withoutMiddleware();
        [$actor] = $this->createActor();

        $service = app(\App\Services\EmployeeImportService::class);
        $this->assertSame(
            count($service->templateHeaders()),
            count($service->templateSampleRow())
        );

        $response = $this->actingAs($actor)->get(route('employees.import.template'));
        $response->assertOk();

        $lines = array_values(array_filter(preg_split("/\r\n|\n|\r/", trim($response->streamedContent())) ?: []));
        $this->assertGreaterThanOrEqual(2, count($lines));

        $headers = str_getcsv($lines[0]);
        $sample = str_getcsv($lines[1]);
        $this->assertSame($service->templateHeaders(), $headers);
        $this->assertSame(count($headers), count($sample));
        $this->assertSame('Engineering', $sample[array_search('department', $headers, true)]);
        $this->assertSame('', $sample[array_search('registration_date', $headers, true)]);
    }

    public function test_import_stops_creating_when_free_seat_limit_reached(): void
    {
        $this->withoutMiddleware();
        Storage::fake('public');
        $this->seed(\Database\Seeders\SubscriptionPlanSeeder::class);

        $plan = \App\Models\SubscriptionPlan::where('name', 'Free')->firstOrFail();
        [$actor] = $this->createActor(subscriptionPlanId: $plan->id);

        for ($i = 0; $i < 2; $i++) {
            User::factory()->create([
                'company_id' => $actor->company_id,
                'user_type' => User::USER_TYPE_STAFF,
                'status' => User::STATUS_ACTIVE,
                'email' => 'existing-'.$i.'-'.Str::uuid().'@demo.local',
            ]);
        }

        $rows = [['name', 'email', 'password']];
        for ($i = 1; $i <= 16; $i++) {
            $rows[] = ['Import User '.$i, 'seat-import-'.$i.'@demo.local', 'password123'];
        }

        $this->actingAs($actor)->post(route('employees.import.store'), [
            'file' => UploadedFile::fake()->createWithContent('over-limit.csv', $this->makeCsv($rows)),
        ])->assertSessionHasNoErrors();

        $import = EmployeeImport::query()->where('company_id', $actor->company_id)->latest('id')->firstOrFail();
        $this->assertSame('completed', $import->status);
        $this->assertSame(8, (int) $import->success_rows);
        $this->assertSame(8, (int) $import->failed_rows);

        $company = Company::with('subscriptionPlan')->findOrFail($actor->company_id);
        $this->assertSame(10, $company->activeEmployeeCount());
        $this->assertFalse($company->canAddEmployees(1));

        $seatFailures = EmployeeImportRow::query()
            ->where('employee_import_id', $import->id)
            ->where('status', 'failed')
            ->where('error_message', 'like', '%seat limit%')
            ->count();
        $this->assertSame(8, $seatFailures);
    }

    /**
     * @return array{0: User, 1: Role}
     */
    private function createActor(?int $subscriptionPlanId = null): array
    {
        $company = Company::query()->create([
            'name' => 'Import Demo Company',
            'short_name' => 'IMPT',
            'email' => 'import-admin-' . Str::uuid() . '@demo.local',
            'status' => 'active',
            'subscription_plan_id' => $subscriptionPlanId,
        ]);

        $role = Role::withoutGlobalScopes()->create([
            'company_id' => $company->id,
            'name' => 'admin',
            'display_name' => 'Admin',
        ]);

        $user = User::factory()->create([
            'company_id' => $company->id,
            'role_id' => $role->id,
            'is_superadmin' => false,
            'user_type' => \App\Models\User::USER_TYPE_ADMIN,
            'status' => 'active',
        ]);

        return [$user, $role];
    }

    /**
     * @param array<int, array<int, string>> $rows
     */
    private function makeCsv(array $rows): string
    {
        $stream = fopen('php://temp', 'r+');
        foreach ($rows as $row) {
            fputcsv($stream, $row);
        }
        rewind($stream);
        $csv = stream_get_contents($stream) ?: '';
        fclose($stream);

        return $csv;
    }
}
