<?php

use App\Exceptions\IdentityCollisionException;
use App\Models\Customer;
use App\Models\Operator;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\URL;

it('hands over a signed link instead of relying on a shared cookie', function () {
    $operator = Operator::factory()->role('Owner')->create();
    $customer = Customer::factory()->create();

    $response = $this->actingAs($operator, 'operator')
        ->post(route('admin.impersonate', $customer));

    // The console and the portal are different hosts in exclusive mode, and a
    // session cookie is host-bound — so the handover cannot be a cookie.
    $response->assertRedirectContains('signature=');
});

it('impersonates a customer who already has a portal login, with the operator session still active throughout', function () {
    // Customer::assertNotAdmin() used to check Auth::guard('operator')->check()
    // instead of inspecting the `users` row it was actually handed — so with
    // the OPERATOR's own session still attached (same host, non-exclusive/
    // shared mode — exactly the setup start() -> enter() runs under here,
    // same test client, same cookies throughout, precisely how a real
    // operator's browser carries its session across both legs in shared
    // mode), ensureUser() threw the instant it found a `users` row that
    // already existed for the customer's email: a 500 on the very first
    // impersonation of anyone who had ever signed into the portal
    // themselves. The guard is gone now (I5) — this is what "gone" has to
    // mean in practice.
    $operator = Operator::factory()->role('Owner')->create();
    $customer = Customer::factory()->create();

    // A portal login that exists independently of any impersonation — e.g.
    // the customer signed up themselves before ever being impersonated.
    User::factory()->create(['email' => $customer->email]);

    $response = $this->actingAs($operator, 'operator')
        ->post(route('admin.impersonate', $customer));

    $location = $response->headers->get('Location');

    $this->get($location)->assertRedirect(route('dashboard'));

    expect(Auth::guard('web')->check())->toBeTrue()
        ->and(Auth::guard('web')->user()->email)->toBe($customer->email);
});

it('refuses to create or link a portal login when the email already belongs to an operator', function () {
    // R21: one address must not represent both identities. This is the
    // provisioning/impersonation-time half of the rule; CreateNewUser enforces
    // the same thing at public registration (see RegisterTest).
    Operator::factory()->create(['email' => 'shared@example.test']);
    $customer = Customer::factory()->create(['email' => 'shared@example.test']);

    expect(fn () => $customer->ensureUser())
        ->toThrow(IdentityCollisionException::class);

    expect(User::query()->where('email', 'shared@example.test')->exists())->toBeFalse()
        ->and($customer->fresh()->user_id)->toBeNull();
});

it('refuses to impersonate into an email that already belongs to an operator, instead of crashing', function () {
    $operator = Operator::factory()->role('Owner')->create();
    Operator::factory()->create(['email' => 'shared@example.test']);
    $customer = Customer::factory()->create(['email' => 'shared@example.test']);

    $url = URL::temporarySignedRoute('impersonate.enter', now()->addSeconds(60), [
        'customer' => $customer->uuid, 'operator' => $operator->uuid,
    ]);

    $this->get($url)->assertStatus(409);

    expect(Auth::guard('web')->check())->toBeFalse();
});

it('signs the customer in on the web guard when the link is followed', function () {
    $operator = Operator::factory()->role('Owner')->create();
    $customer = Customer::factory()->create();
    $user = $customer->ensureUser();

    $url = URL::temporarySignedRoute('impersonate.enter', now()->addSeconds(60), [
        'customer' => $customer->uuid, 'operator' => $operator->uuid,
    ]);

    $this->get($url)->assertRedirect(route('dashboard'));

    expect(Auth::guard('web')->id())->toBe($user->id);
});

it('refuses the same link twice', function () {
    $operator = Operator::factory()->role('Owner')->create();
    $customer = Customer::factory()->create();
    $user = $customer->ensureUser();

    $url = URL::temporarySignedRoute('impersonate.enter', now()->addSeconds(60), [
        'customer' => $customer->uuid, 'operator' => $operator->uuid,
    ]);

    $this->get($url)->assertRedirect(route('dashboard'));
    Auth::guard('web')->logout();

    // A link that still works after use is a password with an expiry date.
    $this->get($url)->assertForbidden();
});

it('refuses an expired link', function () {
    $operator = Operator::factory()->role('Owner')->create();
    $customer = Customer::factory()->create();
    $user = $customer->ensureUser();

    $url = URL::temporarySignedRoute('impersonate.enter', now()->subSecond(), [
        'customer' => $customer->uuid, 'operator' => $operator->uuid,
    ]);

    $this->get($url)->assertForbidden();
});

it('leaves the operator session untouched, so leaving returns to the console', function () {
    $operator = Operator::factory()->role('Owner')->create();
    $customer = Customer::factory()->create();
    $user = $customer->ensureUser();

    $url = URL::temporarySignedRoute('impersonate.enter', now()->addSeconds(60), [
        'customer' => $customer->uuid, 'operator' => $operator->uuid,
    ]);

    $this->actingAs($operator, 'operator')->get($url);

    expect(Auth::guard('operator')->check())->toBeTrue();

    // "Returns to the console" is the point of this test, not merely
    // decoration on its name — redirect()->to(AdminArea::home()) used to
    // resolve that bare path against whatever host the leave() POST itself
    // arrived on (the portal's), not the console's; see the exclusive-mode
    // test below for where that actually bites.
    $this->post(route('impersonate.leave'))->assertRedirect(route('admin.overview'));

    expect(Auth::guard('web')->check())->toBeFalse()
        ->and(Auth::guard('operator')->check())->toBeTrue();
});

it('signs the link against the portal host, not the console host it was issued from', function () {
    // Exclusive mode is where this bites: the console has a hostname to
    // itself, and RestrictAdminHost 404s any non-console route reached
    // through it. impersonate.enter carries no domain of its own, so a link
    // built and signed against the console's root would die the instant it
    // was followed — precisely the cross-host failure this task exists to
    // fix. A test that only checks `signature=` is present does not catch
    // this: on dev it passes anyway, because exclusive mode is off there.
    config()->set('admin_access.hosts', ['admin.example.test']);
    config()->set('admin_access.exclusive', true);
    config()->set('app.url', 'https://app.example.test');

    $operator = Operator::factory()->role('Owner')->create();
    $customer = Customer::factory()->create();
    $user = $customer->ensureUser();

    // Issued exactly as a real operator's click would arrive: on the
    // console's own exclusive host. (Routes are compiled once at boot, under
    // this suite's default non-exclusive ADMIN_HOSTS="" — so the path still
    // carries the fallback /admin prefix; only RestrictAdminHost's own checks
    // read admin_access.* live, per request, which is what is under test.)
    $response = $this->actingAs($operator, 'operator')
        ->post('https://admin.example.test/admin/impersonate/'.$customer->uuid);

    $location = $response->headers->get('Location');

    // The link has to carry the PORTAL's host, not the console's.
    expect(parse_url($location, PHP_URL_HOST))->toBe('app.example.test');

    // And it must still genuinely work when followed: valid signature, signs
    // the customer in.
    $this->get($location)->assertRedirect(route('dashboard'));

    expect(Auth::guard('web')->id())->toBe($user->id);
});
