import { OAuthCreateTokenHandler } from '@app/o-auth/flows/application/create-token/o-auth-create-token.handler';
import { UnsupportedMediaTypeException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { ThrottlerGuard } from '@nestjs/throttler';
import type { Request } from 'express';
import { OAuthCreateTokenController } from './o-auth-create-token.controller';

const FORM_URLENCODED = 'application/x-www-form-urlencoded';

const buildHandlerResponse = (overrides: Partial<Record<string, unknown>> = {}) => ({
  access_token: 'header.payload.signature',
  refresh_token: 'opaque-refresh-token',
  token_type: 'Bearer' as const,
  expires_in: 3600,
  ...overrides,
});

const buildRequest = (overrides: { ip?: string | null } = {}): Request =>
  ({
    ip: overrides.ip ?? '127.0.0.1',
  }) as unknown as Request;

/**
 * Encodes a `clientId:clientSecret` pair as HTTP Basic auth header value (RFC 6749 §2.3.1).
 */
const basic = (clientId: string, clientSecret: string): string =>
  `Basic ${Buffer.from(`${clientId}:${clientSecret}`).toString('base64')}`;

describe('OAuthCreateTokenController (transport — pure mapping)', () => {
  let controller: OAuthCreateTokenController;
  let handler: { main: jest.Mock };

  const buildModule = async (): Promise<void> => {
    handler = { main: jest.fn().mockResolvedValue(buildHandlerResponse()) };

    const module: TestingModule = await Test.createTestingModule({
      controllers: [OAuthCreateTokenController],
      providers: [{ provide: OAuthCreateTokenHandler, useValue: handler }],
    })
      // ThrottlerGuard is the only NestJS guard wired by the controller. We bypass it
      // here — rate-limit behavior is an integration concern (e2e or @nestjs/throttler's
      // own tests), not a transport-mapping concern.
      .overrideGuard(ThrottlerGuard)
      .useValue({ canActivate: () => true })
      .compile();

    controller = module.get(OAuthCreateTokenController);
  };

  describe('content-type enforcement', () => {
    it('throws UnsupportedMediaTypeException when content-type is missing', async () => {
      await buildModule();

      await expect(
        controller.main(basic('cli', 'sec'), undefined, 'jest/test', buildRequest(), {
          grant_type: 'password',
          username: 'u',
          password: 'p',
        }),
      ).rejects.toBeInstanceOf(UnsupportedMediaTypeException);
      expect(handler.main).not.toHaveBeenCalled();
    });

    it('throws UnsupportedMediaTypeException when content-type is application/json', async () => {
      await buildModule();

      await expect(
        controller.main(basic('cli', 'sec'), 'application/json', 'jest/test', buildRequest(), {
          grant_type: 'password',
          username: 'u',
          password: 'p',
        }),
      ).rejects.toBeInstanceOf(UnsupportedMediaTypeException);
    });

    it('accepts content-type with charset suffix', async () => {
      await buildModule();

      await controller.main(
        basic('cli', 'sec'),
        'application/x-www-form-urlencoded; charset=utf-8',
        'jest/test',
        buildRequest(),
        { grant_type: 'password', username: 'u', password: 'p' },
      );

      expect(handler.main).toHaveBeenCalledTimes(1);
    });
  });

  describe('Basic auth parsing', () => {
    it('decodes the Basic header into clientId + clientSecret', async () => {
      await buildModule();

      await controller.main(
        basic('aurora-cli', 'top-secret-12345'),
        FORM_URLENCODED,
        'jest/test',
        buildRequest(),
        { grant_type: 'password', username: 'carlos', password: 'hunter2' },
      );

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({
          clientId: 'aurora-cli',
          clientSecret: 'top-secret-12345',
        }),
      );
    });

    it('forwards EMPTY client credentials when Authorization header is missing', async () => {
      await buildModule();

      await controller.main(undefined, FORM_URLENCODED, 'jest/test', buildRequest(), {
        grant_type: 'password',
        username: 'u',
        password: 'p',
      });

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({ clientId: '', clientSecret: '' }),
      );
    });

    it('forwards EMPTY client credentials when Authorization scheme is not Basic', async () => {
      await buildModule();

      await controller.main('Bearer some-token', FORM_URLENCODED, 'jest/test', buildRequest(), {
        grant_type: 'password',
        username: 'u',
        password: 'p',
      });

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({ clientId: '', clientSecret: '' }),
      );
    });

    it('forwards EMPTY client credentials when Basic header is malformed (no colon)', async () => {
      await buildModule();

      await controller.main(
        `Basic ${Buffer.from('noColonHere').toString('base64')}`,
        FORM_URLENCODED,
        'jest/test',
        buildRequest(),
        { grant_type: 'password', username: 'u', password: 'p' },
      );

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({ clientId: '', clientSecret: '' }),
      );
    });
  });

  describe('grant_type normalization', () => {
    it('maps grant_type=password to canonical PASSWORD', async () => {
      await buildModule();

      await controller.main(basic('cli', 'sec'), FORM_URLENCODED, 'jest/test', buildRequest(), {
        grant_type: 'password',
        username: 'carlos',
        password: 'hunter2',
      });

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({
          grantType: 'PASSWORD',
          rawGrantType: 'password',
          username: 'carlos',
          password: 'hunter2',
        }),
      );
    });

    it('maps grant_type=refresh_token to canonical REFRESH_TOKEN', async () => {
      await buildModule();

      await controller.main(basic('cli', 'sec'), FORM_URLENCODED, 'jest/test', buildRequest(), {
        grant_type: 'refresh_token',
        refresh_token: 'opaque-current',
      });

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({
          grantType: 'REFRESH_TOKEN',
          rawGrantType: 'refresh_token',
          refreshToken: 'opaque-current',
        }),
      );
    });

    it('maps grant_type=authorization_code to canonical AUTHORIZATION_CODE', async () => {
      await buildModule();

      await controller.main(basic('cli', 'sec'), FORM_URLENCODED, 'jest/test', buildRequest(), {
        grant_type: 'authorization_code',
        code: 'auth-code-abc',
        redirect_uri: 'https://client.example.com/callback',
        code_verifier: 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk',
      });

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({
          grantType: 'AUTHORIZATION_CODE',
          rawGrantType: 'authorization_code',
          code: 'auth-code-abc',
          redirectUri: 'https://client.example.com/callback',
          codeVerifier: 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk',
        }),
      );
    });

    it('maps an unknown grant_type to UNSUPPORTED for the handler to reject', async () => {
      await buildModule();

      await controller.main(basic('cli', 'sec'), FORM_URLENCODED, 'jest/test', buildRequest(), {
        grant_type: 'client_credentials',
      });

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({
          grantType: 'UNSUPPORTED',
          rawGrantType: 'client_credentials',
        }),
      );
    });

    it('maps a missing grant_type to UNSUPPORTED', async () => {
      await buildModule();

      await controller.main(basic('cli', 'sec'), FORM_URLENCODED, 'jest/test', buildRequest(), {});

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({ grantType: 'UNSUPPORTED' }),
      );
    });
  });

  describe('request context extraction', () => {
    it('passes through ipAddress from req.ip', async () => {
      await buildModule();

      await controller.main(
        basic('cli', 'sec'),
        FORM_URLENCODED,
        'Mozilla/5.0',
        buildRequest({ ip: '203.0.113.42' }),
        { grant_type: 'password', username: 'u', password: 'p' },
      );

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({ ipAddress: '203.0.113.42', userAgent: 'Mozilla/5.0' }),
      );
    });

    it('passes null ipAddress when req.ip is undefined', async () => {
      await buildModule();

      await controller.main(
        basic('cli', 'sec'),
        FORM_URLENCODED,
        undefined,
        { ip: undefined } as unknown as Request,
        { grant_type: 'password', username: 'u', password: 'p' },
      );

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({ ipAddress: null, userAgent: null }),
      );
    });
  });

  describe('response pass-through', () => {
    it('returns the handler response as-is (no mapping — REST uses RFC 6749 §5.1 snake_case)', async () => {
      await buildModule();
      handler.main.mockResolvedValueOnce(
        buildHandlerResponse({
          access_token: 'aaa.bbb.ccc',
          refresh_token: 'refresh-xyz',
          expires_in: 7200,
        }),
      );

      const result = await controller.main(
        basic('cli', 'sec'),
        FORM_URLENCODED,
        'jest/test',
        buildRequest(),
        { grant_type: 'password', username: 'u', password: 'p' },
      );

      expect(result).toEqual({
        access_token: 'aaa.bbb.ccc',
        refresh_token: 'refresh-xyz',
        token_type: 'Bearer',
        expires_in: 7200,
      });
    });
  });

  describe('payload field extraction', () => {
    it('forwards undefined for non-string body fields', async () => {
      await buildModule();

      await controller.main(basic('cli', 'sec'), FORM_URLENCODED, 'jest/test', buildRequest(), {
        grant_type: 'password',
        // intentionally NOT strings — controller must guard with `typeof === 'string'`
        username: 123,
        password: null,
      });

      expect(handler.main).toHaveBeenCalledWith(
        expect.objectContaining({ username: undefined, password: undefined }),
      );
    });
  });
});
