import type { Response } from 'express';
import { HUB_SESSION_MANAGER } from '@aurora/modules/authentication/domain/ports';
import { ConfigService } from '@nestjs/config';
import { Test, TestingModule } from '@nestjs/testing';
import { OAuthHubSessionService } from './o-auth-hub-session.service';

const STUB_ACCOUNT_ID = 'account-uuid-1234';
const STUB_TOKEN = 'eyJhbGciOiJSUzI1NiJ9.stub';

describe('OAuthHubSessionService', () => {
  let service: OAuthHubSessionService;
  let hubSessionManager: {
    signHubSession: jest.Mock;
    verifyHubSession: jest.Mock;
    verifyAccessToken: jest.Mock;
  };

  const buildModule = async (): Promise<void> => {
    hubSessionManager = {
      signHubSession: jest.fn().mockReturnValue(STUB_TOKEN),
      verifyHubSession: jest.fn().mockReturnValue(STUB_ACCOUNT_ID),
      verifyAccessToken: jest.fn().mockReturnValue(STUB_ACCOUNT_ID),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        OAuthHubSessionService,
        { provide: HUB_SESSION_MANAGER, useValue: hubSessionManager },
        {
          provide: ConfigService,
          useValue: {
            get: jest.fn((key: string, defaultValue?: unknown) => {
              const map: Record<string, unknown> = {
                OAUTH_SESSION_COOKIE_TTL: '28800',
                OAUTH_SESSION_COOKIE_SECURE: 'false',
                OAUTH_SESSION_COOKIE_DOMAIN: undefined,
                OAUTH_SESSION_COOKIE_SAME_SITE: 'lax',
              };
              return map[key] ?? defaultValue;
            }),
          },
        },
      ],
    }).compile();

    service = module.get(OAuthHubSessionService);
  };

  beforeEach(async () => {
    jest.clearAllMocks();
    await buildModule();
  });

  describe('signHubSession', () => {
    it('delegates to hubSessionManager.signHubSession and returns the token', () => {
      const token = service.signHubSession(STUB_ACCOUNT_ID);

      expect(hubSessionManager.signHubSession).toHaveBeenCalledWith(STUB_ACCOUNT_ID);
      expect(token).toBe(STUB_TOKEN);
    });
  });

  describe('verifyHubSession', () => {
    it('returns accountId when the hub session manager returns a non-null value', () => {
      const accountId = service.verifyHubSession(STUB_TOKEN);

      expect(accountId).toBe(STUB_ACCOUNT_ID);
      expect(hubSessionManager.verifyHubSession).toHaveBeenCalledWith(STUB_TOKEN);
    });

    it('returns null when the hub session manager returns null (invalid/expired token)', () => {
      hubSessionManager.verifyHubSession.mockReturnValueOnce(null);

      const accountId = service.verifyHubSession(STUB_TOKEN);

      expect(accountId).toBeNull();
    });
  });

  describe('createSessionCookie', () => {
    it('sets a hub_session cookie with httpOnly, secure, sameSite=lax, and correct maxAge', () => {
      const mockCookieFn = jest.fn();
      const mockRes = { cookie: mockCookieFn } as unknown as Response;

      service.createSessionCookie(mockRes, STUB_ACCOUNT_ID);

      expect(hubSessionManager.signHubSession).toHaveBeenCalledTimes(1);
      expect(mockCookieFn).toHaveBeenCalledWith(
        'hub_session',
        STUB_TOKEN,
        expect.objectContaining({
          httpOnly: true,
          // secure must be present so a regression that drops it is caught.
          // With OAUTH_SESSION_COOKIE_SECURE=false and sameSite=lax, secure should be false.
          secure: false,
          sameSite: 'lax',
          maxAge: 28800 * 1000,
          path: '/',
        }),
      );
    });

    it('forces secure=true when sameSite=none, regardless of OAUTH_SESSION_COOKIE_SECURE', async () => {
      // Browsers drop SameSite=None cookies without Secure. The service must force secure=true
      // even if the env var is set to 'false'.
      const module = await Test.createTestingModule({
        providers: [
          OAuthHubSessionService,
          { provide: HUB_SESSION_MANAGER, useValue: hubSessionManager },
          {
            provide: ConfigService,
            useValue: {
              get: jest.fn((key: string, defaultValue?: unknown) => {
                const map: Record<string, unknown> = {
                  OAUTH_SESSION_COOKIE_TTL: '28800',
                  OAUTH_SESSION_COOKIE_SECURE: 'false', // explicitly false
                  OAUTH_SESSION_COOKIE_DOMAIN: undefined,
                  OAUTH_SESSION_COOKIE_SAME_SITE: 'none', // triggers the force
                };
                return map[key] ?? defaultValue;
              }),
            },
          },
        ],
      }).compile();

      const noneService = module.get(OAuthHubSessionService);
      const mockCookieFn = jest.fn();
      const mockRes = { cookie: mockCookieFn } as unknown as Response;

      noneService.createSessionCookie(mockRes, STUB_ACCOUNT_ID);

      expect(mockCookieFn).toHaveBeenCalledWith(
        'hub_session',
        STUB_TOKEN,
        expect.objectContaining({
          sameSite: 'none',
          secure: true, // must be forced true despite OAUTH_SESSION_COOKIE_SECURE=false
        }),
      );
    });
  });

  describe('TTL NaN guard', () => {
    it('falls back to the 28800-second default when OAUTH_SESSION_COOKIE_TTL is non-numeric', async () => {
      // A misconfigured env var like OAUTH_SESSION_COOKIE_TTL='abc' makes Number('abc') return NaN
      // → maxAge: NaN → browser treats the cookie as a session cookie (no expiry).
      // The NaN guard must fall back to DEFAULT_TTL_SECONDS (28800).
      const module: TestingModule = await Test.createTestingModule({
        providers: [
          OAuthHubSessionService,
          { provide: HUB_SESSION_MANAGER, useValue: hubSessionManager },
          {
            provide: ConfigService,
            useValue: {
              get: jest.fn((key: string, defaultValue?: unknown) => {
                const map: Record<string, unknown> = {
                  OAUTH_SESSION_COOKIE_TTL: 'not-a-number',
                  OAUTH_SESSION_COOKIE_SECURE: 'false',
                  OAUTH_SESSION_COOKIE_DOMAIN: undefined,
                  OAUTH_SESSION_COOKIE_SAME_SITE: 'lax',
                };
                return map[key] ?? defaultValue;
              }),
            },
          },
        ],
      }).compile();

      const nanGuardService = module.get(OAuthHubSessionService);
      const mockCookieFn = jest.fn();
      const mockRes = { cookie: mockCookieFn } as unknown as Response;

      nanGuardService.createSessionCookie(mockRes, STUB_ACCOUNT_ID);

      expect(mockCookieFn).toHaveBeenCalledWith(
        'hub_session',
        STUB_TOKEN,
        expect.objectContaining({
          maxAge: 28800 * 1000, // falls back to DEFAULT_TTL_SECONDS
        }),
      );
    });
  });

  describe('session establishment — verifyAccessTokenAndGetAccountId', () => {
    it('returns null when the access token is invalid', async () => {
      hubSessionManager.verifyAccessToken.mockReturnValueOnce(null);

      const result = await service.verifyAccessTokenAndGetAccountId('bad-token');
      expect(result).toBeNull();
    });

    it('returns the accountId (aci claim) when the access token is valid', async () => {
      hubSessionManager.verifyAccessToken.mockReturnValueOnce(STUB_ACCOUNT_ID);

      const result = await service.verifyAccessTokenAndGetAccountId(STUB_TOKEN);
      expect(result).toBe(STUB_ACCOUNT_ID);
    });
  });
});
