<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

/**
 * Signed page watermark (app/Helpers/watermark_helper.php).
 *
 * The property that matters is that the signature on line 2 can be recomputed
 * from what is printed on screen — that is exactly what someone tracing a leaked
 * screenshot does. So rather than freezing the clock, these tests read the
 * timestamp back off line 1 and re-derive the HMAC, which is the real contract.
 *
 * Runs on the lightweight tests/bootstrap.php (no CI4 boot): the auth + watermark
 * helpers are required directly and the session comes from the bootstrap's
 * session() stub. service('request') is absent here, so the domain resolves to ''
 * — the signature still has to verify over that empty host.
 */
final class WatermarkTest extends TestCase
{
    /** Test-only key; the real one lives in app/Config/Constants.php. */
    private const SECRET = 'unit-test-watermark-secret';

    public static function setUpBeforeClass(): void
    {
        if (!defined('WATERMARK_SECRET')) {
            define('WATERMARK_SECRET', self::SECRET);
        }
        require_once dirname(__DIR__, 2) . '/app/Helpers/auth_helper.php';
        require_once dirname(__DIR__, 2) . '/app/Helpers/watermark_helper.php';
    }

    protected function setUp(): void
    {
        parent::setUp();
        $GLOBALS['__test_session__'] = [];
    }

    protected function tearDown(): void
    {
        $GLOBALS['__test_session__'] = [];
        parent::tearDown();
    }

    /** Recompute the signature the way a verifier would, from the rendered lines. */
    private function assertSignatureVerifies(array $wm, string $expectedUser): void
    {
        $this->assertMatchesRegularExpression(
            '/^' . preg_quote($expectedUser, '/') . ' - \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/',
            $wm['line1'],
            'line1 must read "<username> - <Y-m-d H:i:s>"'
        );

        $date = substr($wm['line1'], strlen($expectedUser . ' - '));

        $parts     = explode(' - ', $wm['line2'], 2);
        $signature = trim($parts[0]);
        $domain    = isset($parts[1]) ? trim($parts[1]) : '';
        $this->assertSame(12, strlen($signature), 'line2 carries 12 hex characters');

        $expected = substr(
            hash_hmac('sha256', $expectedUser . $date . $domain, constant('WATERMARK_SECRET')),
            0,
            12
        );
        $this->assertSame($expected, $signature, 'signature must verify against WATERMARK_SECRET');
    }

    public function testAdvertiserSessionIsSignedAndHasNoImpersonationLine(): void
    {
        $GLOBALS['__test_session__'] = ['username' => 'Jane Advertiser'];

        $wm = getSignedWatermark();

        $this->assertSignatureVerifies($wm, 'Jane Advertiser');
        $this->assertSame('', $wm['line3'], 'a direct advertiser login has nothing to impersonate');
    }

    /**
     * Legacy parity (advertisers7 views/layouts/default.php:12-20): while a mngt
     * user is impersonating, the signed line names the ADVERTISER being viewed and
     * the portal user gets its own "Login as:" line. session('username') is the
     * portal user on that path, so signing it would name the wrong party.
     */
    public function testMngtSessionSignsTheAdvertiserNotThePortalUser(): void
    {
        $GLOBALS['__test_session__'] = [
            'adv_id'       => 17237,
            'username'     => 'Mason Leavey',   // the mngt portal user
            'account_name' => 'State Farm',     // the advertiser being viewed
            'mngtuser'     => 'masonl',
        ];

        $wm = getSignedWatermark();

        $this->assertSignatureVerifies($wm, 'State Farm');
        $this->assertSame('Login as: masonl', $wm['line3'], 'the portal user gets its own line');
        $this->assertStringNotContainsString(
            'Mason Leavey',
            $wm['line1'],
            'the portal user must not take the advertiser line'
        );
    }

    /**
     * A direct login is signed with the LOGIN USERNAME (Advertiser_info.Advertiser,
     * which legacy Auth_model::validate() authenticates against) — not
     * firstname + lastname. Real dev shapes: account "languageschool" belongs to
     * firstname "J and C", lastname "Language School".
     */
    public function testDirectLoginSignsTheLoginUsernameNotThePersonName(): void
    {
        $GLOBALS['__test_session__'] = [
            'adv_id'       => 22150,
            'username'     => 'J and C Language School', // firstname + lastname
            'account_name' => 'languageschool',          // the login username
        ];

        $wm = getSignedWatermark();

        $this->assertSignatureVerifies($wm, 'languageschool');
        $this->assertSame('', $wm['line3'], 'a direct login has nothing to impersonate');
        $this->assertStringNotContainsString(
            'J and C',
            $wm['line1'],
            'the person name must not displace the login username'
        );
    }

    public function testMngtSessionWithNoAccountNameFallsBackToThePortalUser(): void
    {
        $GLOBALS['__test_session__'] = ['username' => 'Mason Leavey', 'mngtuser' => 'masonl'];

        $this->assertSignatureVerifies(getSignedWatermark(), 'masonl');
    }

    public function testFallsBackToTheMngtUserThenGuest(): void
    {
        $GLOBALS['__test_session__'] = ['mngtuser' => 'qabot'];
        $this->assertSignatureVerifies(getSignedWatermark(), 'qabot');

        $GLOBALS['__test_session__'] = [];
        $this->assertSignatureVerifies(getSignedWatermark(), 'guest');
    }

    public function testAnExplicitUsernameOverridesTheSession(): void
    {
        $GLOBALS['__test_session__'] = ['username' => 'Jane Advertiser'];

        $this->assertSignatureVerifies(getSignedWatermark('Someone Else'), 'Someone Else');
    }

    public function testSignatureIsKeyedToTheSecret(): void
    {
        $GLOBALS['__test_session__'] = ['username' => 'Jane Advertiser'];

        $wm        = getSignedWatermark();
        $parts     = explode(' - ', $wm['line2'], 2);
        $signature = trim($parts[0]);
        $domain    = isset($parts[1]) ? trim($parts[1]) : '';
        $date      = substr($wm['line1'], strlen('Jane Advertiser - '));

        $withWrongKey = substr(
            hash_hmac('sha256', 'Jane Advertiser' . $date . $domain, 'not-the-secret'),
            0,
            12
        );
        $this->assertNotSame($withWrongKey, $signature, 'a wrong key must not reproduce the signature');
    }

    public function testUsernameIsTrimmedSoTheSignedStringMatchesWhatIsShown(): void
    {
        $GLOBALS['__test_session__'] = ['username' => '  Jane Advertiser  '];

        $this->assertSignatureVerifies(getSignedWatermark(), 'Jane Advertiser');
    }
}
