import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { UnauthorizedException } from '@nestjs/common';
import { JwtStrategy } from './jwt.strategy';
import { LaravelApiService } from '../../laravel-api/laravel-api.service';
import { JwtPayload } from '../interfaces/jwt-payload.interface';

describe('JwtStrategy', () => {
  let strategy: JwtStrategy;
  let configService: ConfigService;
  let laravelApiService: LaravelApiService;

  beforeEach(async () => {
    const mockConfigService = {
      get: jest.fn().mockImplementation((key: string) => {
        const config = {
          JWT_SECRET: 'test-secret',
          JWT_ALGORITHM: 'HS256',
          JWT_TTL: 3600,
        };
        return config[key];
      }),
    };

    const mockLaravelApiService = {
      // Mock methods if needed
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        JwtStrategy,
        {
          provide: ConfigService,
          useValue: mockConfigService,
        },
        {
          provide: LaravelApiService,
          useValue: mockLaravelApiService,
        },
      ],
    }).compile();

    strategy = module.get<JwtStrategy>(JwtStrategy);
    configService = module.get<ConfigService>(ConfigService);
    laravelApiService = module.get<LaravelApiService>(LaravelApiService);
  });

  it('should be defined', () => {
    expect(strategy).toBeDefined();
  });

  describe('validate', () => {
    const validPayload: JwtPayload = {
      iss: 'test-issuer',
      iat: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
      exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
      nbf: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
      sub: 'user_123',
      jti: 'jwt-id-123',
      prv: 'users',
    };

    it('should return authenticated user for valid user payload', () => {
      const result = strategy.validate(validPayload);

      expect(result).toHaveProperty('id', 'user_123');
      expect(result).toHaveProperty('type', 'user');
      expect(result).toHaveProperty('payload', validPayload);
      expect(result).toHaveProperty('expiresAt');
      expect(result).toHaveProperty('issuedAt');
      expect(result).toHaveProperty('tokenData');
      expect(result).toHaveProperty('rawToken', null);
      expect(result.expiresAt).toBeInstanceOf(Date);
      expect(result.issuedAt).toBeInstanceOf(Date);
    });

    it('should return authenticated user for valid client payload', () => {
      const clientPayload: JwtPayload = {
        ...validPayload,
        sub: 'client_123',
        prv: 'hash123',
      };

      const result = strategy.validate(clientPayload);

      expect(result).toHaveProperty('id', 'client_123');
      expect(result).toHaveProperty('type', 'client');
      expect(result).toHaveProperty('payload', clientPayload);
    });

    it('should throw UnauthorizedException for missing required claims', () => {
      const invalidPayload = {
        iss: 'test-issuer',
        // Missing iat, exp, nbf, sub, jti
      } as JwtPayload;

      expect(() => {
        strategy.validate(invalidPayload);
      }).toThrow(UnauthorizedException);
    });

    it('should throw UnauthorizedException for expired token', () => {
      const expiredPayload: JwtPayload = {
        ...validPayload,
        exp: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago
      };

      expect(() => {
        strategy.validate(expiredPayload);
      }).toThrow(UnauthorizedException);
      expect(() => {
        strategy.validate(expiredPayload);
      }).toThrow('Token expirado');
    });

    it('should throw UnauthorizedException for token not yet valid (nbf)', () => {
      const futurePayload: JwtPayload = {
        ...validPayload,
        nbf: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now
      };

      expect(() => {
        strategy.validate(futurePayload);
      }).toThrow(UnauthorizedException);
      expect(() => {
        strategy.validate(futurePayload);
      }).toThrow('Token ainda não é válido (nbf)');
    });

    it('should determine user type from prv field', () => {
      const userPayload: JwtPayload = {
        ...validPayload,
        prv: 'users',
      };

      const result = strategy.validate(userPayload);
      expect(result.type).toBe('user');

      const clientPayload: JwtPayload = {
        ...validPayload,
        prv: 'clients',
      };

      const clientResult = strategy.validate(clientPayload);
      expect(clientResult.type).toBe('client');
    });

    it('should determine user type from issuer field', () => {
      const userPayload: JwtPayload = {
        ...validPayload,
        prv: undefined,
        iss: 'user-service',
      };

      const result = strategy.validate(userPayload);
      expect(result.type).toBe('user');

      const clientPayload: JwtPayload = {
        ...validPayload,
        prv: undefined,
        iss: 'client-service',
      };

      const clientResult = strategy.validate(clientPayload);
      expect(clientResult.type).toBe('client');
    });

    it('should determine user type from subject field', () => {
      const userPayload: JwtPayload = {
        ...validPayload,
        prv: undefined,
        iss: 'unknown-service',
        sub: 'user_123',
      };

      const result = strategy.validate(userPayload);
      expect(result.type).toBe('user');

      const clientPayload: JwtPayload = {
        ...validPayload,
        prv: undefined,
        iss: 'unknown-service',
        sub: 'client_123',
      };

      const clientResult = strategy.validate(clientPayload);
      expect(clientResult.type).toBe('client');
    });

    it('should default to user type when cannot determine', () => {
      const unknownPayload: JwtPayload = {
        ...validPayload,
        prv: undefined,
        iss: 'unknown-service',
        sub: 'unknown_123',
      };

      const result = strategy.validate(unknownPayload);
      expect(result.type).toBe('user');
    });

    it('should extract token data correctly', () => {
      const payloadWithCustomFields: JwtPayload = {
        ...validPayload,
        uuid: 'uuid-123',
        email: 'test@example.com',
        name: 'Test User',
        role: 'admin',
        permissions: ['read', 'write'],
        company_id: 'company_123',
        client_id: 'client_123',
      } as any;

      const result = strategy.validate(payloadWithCustomFields);

      expect(result.tokenData).toHaveProperty('issuer', 'test-issuer');
      expect(result.tokenData).toHaveProperty('jwtId', 'jwt-id-123');
      expect(result.tokenData).toHaveProperty('issuedAt');
      expect(result.tokenData).toHaveProperty('expiresAt');
      expect(result.tokenData).toHaveProperty('notBefore');
      expect(result.tokenData).toHaveProperty('subject', 'user_123');
      expect(result.tokenData).toHaveProperty('provider', 'users');
      expect(result.tokenData).toHaveProperty('uuid', 'uuid-123');
      expect(result.tokenData).toHaveProperty('email', 'test@example.com');
      expect(result.tokenData).toHaveProperty('name', 'Test User');
      expect(result.tokenData).toHaveProperty('role', 'admin');
      expect(result.tokenData).toHaveProperty('permissions', ['read', 'write']);
      expect(result.tokenData).toHaveProperty('company_id', 'company_123');
      expect(result.tokenData).toHaveProperty('client_id', 'client_123');
    });

    it('should handle payload without custom fields', () => {
      const result = strategy.validate(validPayload);

      expect(result.tokenData).toHaveProperty('issuer', 'test-issuer');
      expect(result.tokenData).toHaveProperty('jwtId', 'jwt-id-123');
      expect(result.tokenData).toHaveProperty('subject', 'user_123');
      expect(result.tokenData).toHaveProperty('provider', 'users');
      expect(result.tokenData).not.toHaveProperty('uuid');
      expect(result.tokenData).not.toHaveProperty('email');
      expect(result.tokenData).not.toHaveProperty('name');
    });
  });

  describe('M0 S5 — no default-secret fallback', () => {
    it('throws when JWT_SECRET is missing instead of using a default secret', () => {
      const mockConfigService = {
        get: jest.fn((key: string) =>
          key === 'JWT_ALGORITHM' ? 'HS256' : undefined,
        ),
      };

      const mockLaravelApiService = {} as LaravelApiService;

      expect(
        () =>
          new JwtStrategy(
            mockConfigService as unknown as ConfigService,
            mockLaravelApiService,
          ),
      ).toThrow(/JWT_SECRET/);
    });

    it('constructs when JWT_SECRET is present', () => {
      const mockConfigService = {
        get: jest.fn((key: string) => {
          if (key === 'JWT_SECRET') return 'a'.repeat(32);
          if (key === 'JWT_ALGORITHM') return 'HS256';
          return undefined;
        }),
      };

      const mockLaravelApiService = {} as LaravelApiService;

      expect(
        () =>
          new JwtStrategy(
            mockConfigService as unknown as ConfigService,
            mockLaravelApiService,
          ),
      ).not.toThrow();
    });
  });
});
