<?php

declare(strict_types=1);

namespace App\Tests\Functional\Controller;

use App\Entity\Device;
use App\Entity\DeviceImageHistory;
use App\Entity\Image;
use App\Enum\DeviceModel;
use App\Enum\Orientation;
use App\Tests\Functional\AppWebTestCase;

class SetupControllerTest extends AppWebTestCase
{
    private const MAC = 'AA:BB:CC:00:11:22';

    // S-01: anonymous GET /setup/{mac} → 200 (shows login/register form)
    public function test_setup_index_anonymous_renders(): void
    {
        $this->client->request('GET', '/setup/' . self::MAC);
        $this->assertResponseIsSuccessful();
    }

    // S-02: authenticated with named device → redirects to spa (/)
    public function test_setup_index_authenticated_named_device_redirects_to_spa(): void
    {
        $user = $this->createUser('setup02@example.com');

        $device = new Device();
        $device->setMac(self::MAC);
        $device->setName('Living Room');
        $device->setUser($user);
        $this->em()->persist($device);
        $this->em()->flush();

        $this->loginAs($user);
        $this->client->request('GET', '/setup/' . self::MAC);

        $this->assertResponseRedirects('/');
    }

    // S-03: authenticated with no existing device → links and redirects to
    // /?setup=<id> so the SPA opens its settings sheet for first-time setup.
    public function test_setup_index_authenticated_new_device_redirects_to_spa_setup(): void
    {
        $user = $this->createUser('setup03@example.com');
        $this->loginAs($user);

        $this->client->request('GET', '/setup/' . self::MAC);

        $this->assertResponseRedirects();
        $location = $this->client->getResponse()->headers->get('Location');
        $this->assertStringContainsString('/?setup=', $location, 'redirects to SPA with setup query');
    }

    // S-04: POST /register links + redirects to SPA with ?setup=<id>.
    public function test_setup_register_creates_user_and_redirects_to_spa_setup(): void
    {
        $this->client->request('POST', '/setup/' . self::MAC . '/register', [
            'registration_form' => [
                'email'         => 'setupnew@example.com',
                'plainPassword' => [
                    'first'  => 'securepass123',
                    'second' => 'securepass123',
                ],
            ],
        ]);

        $this->assertResponseRedirects();
        $this->assertStringContainsString('/?setup=', $this->client->getResponse()->headers->get('Location'));
    }

    // S-05: legacy /configure POST is now a redirect to the SPA — the
    // first-time settings UI lives in the live PWA, not Twig.
    public function test_legacy_configure_post_redirects_to_spa_setup(): void
    {
        $user = $this->createUser('setup05@example.com');

        $device = new Device();
        $device->setMac(self::MAC);
        $device->setUser($user);
        $this->em()->persist($device);
        $this->em()->flush();

        $this->loginAs($user);
        // Old form POST — controller doesn't process the body, just redirects.
        $this->client->request('POST', '/setup/' . self::MAC . '/configure', [
            'name' => 'Kitchen Frame',
        ]);

        $this->assertResponseRedirects();
        $this->assertStringContainsString('/?setup=', $this->client->getResponse()->headers->get('Location'));
    }

    // S-06: /login → links + redirects to SPA setup.
    public function test_login_valid_credentials_redirects_to_spa_setup(): void
    {
        $this->createUser('setuplogin@example.com', 'testpass');

        $this->client->request('POST', '/setup/' . self::MAC . '/login', [
            '_username' => 'setuplogin@example.com',
            '_password' => 'testpass',
        ]);

        $this->assertResponseRedirects();
        $this->assertStringContainsString('/?setup=', $this->client->getResponse()->headers->get('Location'));
    }

    // S-07: POST /setup/{mac}/login with wrong password → redirects to index
    public function test_login_invalid_credentials_redirects_to_index(): void
    {
        $this->createUser('setupbadlogin@example.com', 'testpass');

        $this->client->request('POST', '/setup/' . self::MAC . '/login', [
            '_username' => 'setupbadlogin@example.com',
            '_password' => 'wrongpass',
        ]);

        $this->assertResponseRedirects('/setup/' . self::MAC);
    }

    // S-08: GET /setup/{mac}/configure also redirects (legacy, kept for
    // any in-flight bookmarks).
    public function test_legacy_configure_get_redirects_to_spa(): void
    {
        $user = $this->createUser('setupconfigget@example.com');

        $device = new Device();
        $device->setMac(self::MAC);
        $device->setUser($user);
        $this->em()->persist($device);
        $this->em()->flush();

        $this->loginAs($user);
        $this->client->request('GET', '/setup/' . self::MAC . '/configure');

        $this->assertResponseRedirects();
        $this->assertStringContainsString('/?setup=', $this->client->getResponse()->headers->get('Location'));
    }


    // S-10: POST /setup/{mac}/register with invalid form data → re-renders registration page
    public function test_setup_register_invalid_form_renders_page(): void
    {
        // Valid email (avoids TypeError) but mismatched passwords → form is invalid
        $this->client->request('POST', '/setup/' . self::MAC . '/register', [
            'registration_form' => [
                'email'         => 'setup-invalid@example.com',
                'plainPassword' => [
                    'first'  => 'password123',
                    'second' => 'different456',
                ],
            ],
        ]);

        // Symfony 7 returns 422 for submitted-but-invalid forms
        $this->assertResponseStatusCodeSame(422);
    }

    // ── Sell-to-friend / claim-device flow ───────────────────────────────

    private function makeBoundDevice(string $mac, string $ownerEmail): array
    {
        $owner = $this->createUser($ownerEmail);
        $device = new Device();
        $device->setMac($mac);
        $device->setName('Old Owner Frame');
        $device->setUser($owner);
        $device->setModel(DeviceModel::V1);
        $device->setOrientation(Orientation::Landscape);
        $this->em()->persist($device);
        $this->em()->flush();
        return [$owner, $device];
    }

    // S-CLAIM-01: GET /setup/{mac} for an already-bound MAC shows the claim
    // checkbox so the new owner has to acknowledge what they're erasing.
    public function test_setup_index_shows_claim_checkbox_when_mac_already_bound(): void
    {
        [, $device] = $this->makeBoundDevice(self::MAC, 'old-owner@example.com');

        $crawler = $this->client->request('GET', '/setup/' . self::MAC);
        $this->assertResponseIsSuccessful();
        $this->assertSelectorTextContains('.claim-banner', 'already linked to another account');
        // Checkbox appears in BOTH the register and login panels.
        $this->assertCount(2, $crawler->filter('input[name="claim_device"][type="checkbox"]'));
    }

    // S-CLAIM-02: POST /register without claim_device on a bound MAC bounces
    // back through the setup index with an error in session.
    public function test_register_without_claim_checkbox_bounces_with_error(): void
    {
        $this->makeBoundDevice(self::MAC, 'old-claim02@example.com');

        $this->client->request('POST', '/setup/' . self::MAC . '/register', [
            'registration_form' => [
                'email'         => 'new-claim02@example.com',
                'plainPassword' => [
                    'first'  => 'secretpass1',
                    'second' => 'secretpass1',
                ],
            ],
        ]);
        $this->assertResponseRedirects('/setup/' . self::MAC);

        // Follow the redirect and assert the error surfaces.
        $this->client->followRedirect();
        $this->assertSelectorTextContains('.field-error', 'already linked');

        // Device ownership should NOT have transferred.
        $this->em()->clear();
        $reloaded = $this->em()->getRepository(Device::class)->findOneBy(['mac' => self::MAC]);
        $this->assertNotNull($reloaded->getUser());
        $this->assertSame('old-claim02@example.com', $reloaded->getUser()->getEmail());
    }

    // S-CLAIM-03: POST /register WITH claim_device=1 transfers ownership and
    // purges old image history + image-device approvals.
    public function test_register_with_claim_checkbox_transfers_ownership_and_purges_history(): void
    {
        [$oldOwner, $device] = $this->makeBoundDevice(self::MAC, 'old-claim03@example.com');

        // Old owner has an image approved for this device + a history row.
        $image = (new Image())->setUser($oldOwner)->setOriginalFilename('p.jpg')->setStoragePath('p');
        $image->approveForDevice($device);
        $this->em()->persist($image);
        $history = new DeviceImageHistory($device, $image);
        $this->em()->persist($history);
        $device->setName('Living Room')->setWakeTimes([6 * 60]);
        $this->em()->flush();

        $deviceId = $device->getId();

        $this->client->request('POST', '/setup/' . self::MAC . '/register', [
            'registration_form' => [
                'email'         => 'new-claim03@example.com',
                'plainPassword' => [
                    'first'  => 'secretpass1',
                    'second' => 'secretpass1',
                ],
            ],
            'claim_device' => '1',
        ]);
        $this->assertResponseRedirects();
        $this->assertStringContainsString('/?setup=', $this->client->getResponse()->headers->get('Location'));

        $this->em()->clear();
        $reloaded = $this->em()->find(Device::class, $deviceId);
        $this->assertSame('new-claim03@example.com', $reloaded->getUser()->getEmail(), 'ownership transferred');
        $this->assertSame('', $reloaded->getName(), 'name reset on takeover');
        $this->assertSame([12 * 60], $reloaded->getWakeTimes(), 'wakeTimes reset to noon-daily default');

        // Old history is gone.
        $count = (int) $this->em()->createQueryBuilder()
            ->select('COUNT(h.id)')
            ->from(DeviceImageHistory::class, 'h')
            ->where('h.device = :d')
            ->setParameter('d', $reloaded)
            ->getQuery()
            ->getSingleScalarResult();
        $this->assertSame(0, $count, 'history purged');

        // Old image's approval for this device is gone too.
        $reloadedImage = $this->em()->find(Image::class, $image->getId());
        $approvedIds = array_map(
            fn(Device $d) => $d->getId(),
            $reloadedImage->getApprovedDevices()->toArray(),
        );
        $this->assertNotContains($deviceId, $approvedIds, 'image-device approval revoked');
    }

    // S-CLAIM-04: POST /login without claim_device on a bound MAC bounces
    // back with the error (login still happens — the user is now logged in,
    // but the device transfer didn't go through).
    public function test_login_without_claim_checkbox_bounces_with_error(): void
    {
        $this->makeBoundDevice(self::MAC, 'old-claim04@example.com');
        $newOwner = $this->createUser('new-claim04@example.com');

        $this->client->request('POST', '/setup/' . self::MAC . '/login', [
            '_username' => 'new-claim04@example.com',
            '_password' => 'password',
        ]);
        $this->assertResponseRedirects('/setup/' . self::MAC);
        $this->client->followRedirect();
        $this->assertSelectorTextContains('.field-error', 'already linked');

        $this->em()->clear();
        $reloaded = $this->em()->getRepository(Device::class)->findOneBy(['mac' => self::MAC]);
        $this->assertSame('old-claim04@example.com', $reloaded->getUser()->getEmail(), 'no transfer without checkbox');
    }

    // S-CLAIM-05: GET /setup/{mac} for an already-logged-in user who is NOT
    // the current owner falls through to the form (showing the checkbox)
    // rather than silently transferring on visit. This is the "sold to a
    // friend whose phone is already logged in" scenario.
    public function test_index_falls_through_to_form_when_logged_in_user_is_not_current_owner(): void
    {
        $this->makeBoundDevice(self::MAC, 'old-claim05@example.com');
        $other = $this->createUser('other-claim05@example.com');
        $this->loginAs($other);

        $this->client->request('GET', '/setup/' . self::MAC);
        $this->assertResponseIsSuccessful();
        $this->assertSelectorTextContains('.claim-banner', 'already linked');
    }
}
