<?php

namespace Tests\Feature;

use App\Mail\UserInvitationMail;
use App\Models\Central\Company;
use App\Models\Central\Plan;
use App\Models\Tenant\User;
use App\Services\RbacSeeder;
use App\Tenancy\TenantManager;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Mail;
use Tests\TestCase;

class InvitationFlowTest extends TestCase
{
    use RefreshDatabase;

    private Company $company;

    protected function setUp(): void
    {
        parent::setUp();
        $plan = Plan::firstOrCreate(['slug' => 'test'], ['name' => 'T', 'price' => 0, 'features' => []]);
        $this->company = Company::create([
            'plan_id' => $plan->id, 'slug' => 'inv-' . uniqid(),
            'name' => 'Inv Co', 'status' => 'active',
        ]);
        app(TenantManager::class)->setCurrent($this->company);
        app(RbacSeeder::class)->seedTenantRoles($this->company->id);
    }

    public function test_send_invitation_generates_token_marks_pending_and_queues_mail(): void
    {
        Mail::fake();
        $u = User::create(['name' => 'X', 'email' => 'x@e.com', 'password' => bcrypt('x'), 'role' => 'mechanic', 'status' => 'active']);

        $token = $u->sendInvitation();

        $u->refresh();
        $this->assertNotEmpty($token);
        $this->assertEquals(64, strlen($token));
        $this->assertNotNull($u->invited_at);
        $this->assertNull($u->accepted_at);
        $this->assertEquals('inactive', $u->status);
        $this->assertTrue($u->isPendingInvitation());

        // Stored token is sha256 hash, not the raw value
        $this->assertNotEquals($token, $u->invitation_token);
        $this->assertEquals(hash('sha256', $token), $u->invitation_token);

        Mail::assertQueued(UserInvitationMail::class, fn ($m) => $m->user->id === $u->id);
    }

    public function test_find_by_invitation_token_resolves_user(): void
    {
        Mail::fake();
        $u = User::create(['name' => 'X', 'email' => 'x@e.com', 'password' => bcrypt('x'), 'role' => 'mechanic', 'status' => 'active']);
        $token = $u->sendInvitation();

        $found = User::findByInvitationToken($token);
        $this->assertNotNull($found);
        $this->assertEquals($u->id, $found->id);

        // Wrong token returns null
        $this->assertNull(User::findByInvitationToken('not-a-real-token'));
    }

    public function test_accept_invitation_sets_password_clears_token_activates(): void
    {
        Mail::fake();
        $u = User::create(['name' => 'X', 'email' => 'x@e.com', 'password' => bcrypt('placeholder'), 'role' => 'mechanic', 'status' => 'inactive']);
        $token = $u->sendInvitation();

        $u->refresh();
        $u->acceptInvitation('NewStrongPass123');

        $u->refresh();
        $this->assertNull($u->invitation_token);
        $this->assertNotNull($u->accepted_at);
        $this->assertEquals('active', $u->status);
        $this->assertNotNull($u->email_verified_at);
        $this->assertTrue(\Hash::check('NewStrongPass123', $u->password));
        $this->assertFalse($u->isPendingInvitation());
    }

    public function test_invitation_url_returns_form_with_token(): void
    {
        Mail::fake();
        $u = User::create(['name' => 'X', 'email' => 'x@e.com', 'password' => bcrypt('x'), 'role' => 'mechanic', 'status' => 'active']);
        $token = $u->sendInvitation();

        $resp = $this->get("/invitations/{$token}");
        $resp->assertStatus(200);
        $resp->assertSee($u->name);
        $resp->assertSee('Activează contul');
    }

    public function test_invitation_post_accepts_password_and_redirects(): void
    {
        Mail::fake();
        $u = User::create(['name' => 'X', 'email' => 'x@e.com', 'password' => bcrypt('placeholder'), 'role' => 'mechanic', 'status' => 'active']);
        $token = $u->sendInvitation();

        $resp = $this->post("/invitations/{$token}", [
            'password' => 'BrandNewPass1',
            'password_confirmation' => 'BrandNewPass1',
        ]);
        $resp->assertStatus(302);

        $u->refresh();
        $this->assertNotNull($u->accepted_at);
        $this->assertTrue(\Hash::check('BrandNewPass1', $u->password));
    }

    public function test_invalid_token_returns_invalid_view(): void
    {
        $resp = $this->get('/invitations/not-a-real-token');
        $resp->assertStatus(200);
        $resp->assertSee('Invitație invalidă');
    }

    public function test_expired_invitation_returns_expired_view(): void
    {
        Mail::fake();
        $u = User::create(['name' => 'X', 'email' => 'x@e.com', 'password' => bcrypt('x'), 'role' => 'mechanic', 'status' => 'active']);
        $token = $u->sendInvitation();
        // Force expiry by backdating invited_at
        $u->forceFill(['invited_at' => now()->subDays(8)])->saveQuietly();

        $resp = $this->get("/invitations/{$token}");
        $resp->assertStatus(200);
        $resp->assertSee('Invitație expirată');
    }

    public function test_password_too_short_returns_validation_error(): void
    {
        Mail::fake();
        $u = User::create(['name' => 'X', 'email' => 'x@e.com', 'password' => bcrypt('x'), 'role' => 'mechanic', 'status' => 'active']);
        $token = $u->sendInvitation();

        $resp = $this->post("/invitations/{$token}", [
            'password' => 'short',
            'password_confirmation' => 'short',
        ]);
        $resp->assertSessionHasErrors('password');

        $u->refresh();
        $this->assertNull($u->accepted_at);
    }
}
