import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { CheckAuthenticationUseCase } from 'apps/companion/main/automation/application/use-cases/CheckAuthenticationUseCase';
import { AuthenticationState } from 'apps/companion/main/automation/domain/value-objects/AuthenticationState';
import { BrowserAuthenticationState } from 'apps/companion/main/automation/domain/value-objects/BrowserAuthenticationState';
import { Result } from '@core/shared/application/Result';
import type { AuthenticationServicePort } from 'apps/companion/main/automation/application/ports/AuthenticationServicePort';

interface ISessionValidator {
  validateSession(): Promise<Result<boolean>>;
}

describe('CheckAuthenticationUseCase', () => {
  let mockAuthService: {
    checkSession: Mock;
    initiateLogin: Mock;
    clearSession: Mock;
    getState: Mock;
    validateServerSide: Mock;
    refreshSession: Mock;
    getSessionExpiry: Mock;
    verifyPageAuthentication: Mock;
  };
  let mockSessionValidator: {
    validateSession: Mock;
  };

  beforeEach(() => {
    mockAuthService = {
      checkSession: vi.fn(),
      initiateLogin: vi.fn(),
      clearSession: vi.fn(),
      getState: vi.fn(),
      validateServerSide: vi.fn(),
      refreshSession: vi.fn(),
      getSessionExpiry: vi.fn(),
      verifyPageAuthentication: vi.fn(),
    };

    mockSessionValidator = {
      validateSession: vi.fn(),
    };
  });

  describe('File-based validation only', () => {
    it('should return AUTHENTICATED when cookies are valid', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.AUTHENTICATED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() + 3600000))
      );

      const result = await useCase.execute();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
      expect(mockAuthService.checkSession).toHaveBeenCalledTimes(1);
    });

    it('should return EXPIRED when cookies are expired', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.EXPIRED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() - 3600000))
      );

      const result = await useCase.execute();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
    });

    it('should return UNKNOWN when no session exists', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.UNKNOWN)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(null)
      );

      const result = await useCase.execute();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toBe(AuthenticationState.UNKNOWN);
    });
  });

  describe('Server-side validation enabled', () => {
    it('should confirm AUTHENTICATED when file and server both validate', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort,
        mockSessionValidator as unknown as ISessionValidator
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.AUTHENTICATED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() + 3600000))
      );
      mockSessionValidator.validateSession.mockResolvedValue(
        Result.ok(true)
      );

      const result = await useCase.execute();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
      expect(mockSessionValidator.validateSession).toHaveBeenCalledTimes(1);
    });

    it('should return EXPIRED when file says valid but server rejects', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort,
        mockSessionValidator as unknown as ISessionValidator
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.AUTHENTICATED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() + 3600000))
      );
      mockSessionValidator.validateSession.mockResolvedValue(
        Result.ok(false)
      );

      const result = await useCase.execute();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
    });

    it('should work without ISessionValidator injected (optional dependency)', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.AUTHENTICATED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() + 3600000))
      );

      const result = await useCase.execute();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
    });
  });

  describe('Error handling', () => {
    it('should not block file-based result if server validation fails', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort,
        mockSessionValidator as unknown as ISessionValidator
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.AUTHENTICATED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() + 3600000))
      );
      mockSessionValidator.validateSession.mockResolvedValue(
        Result.err('Network error')
      );

      const result = await useCase.execute();

      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
    });

    it('should handle authentication service errors gracefully', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.err('File read error')
      );

      const result = await useCase.execute();

      expect(result.isErr()).toBe(true);
      expect(result.unwrapErr()).toContain('File read error');
    });

    it('should handle session expiry check errors gracefully', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.AUTHENTICATED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.err('Invalid session format')
      );

      const result = await useCase.execute();

      // Should not block on expiry check errors, return file-based state
      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
    });
  });

  describe('Page content verification', () => {
    it('should call verifyPageAuthentication when verifyPageContent is true', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.AUTHENTICATED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() + 3600000))
      );
      mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
        Result.ok(new BrowserAuthenticationState(true, true))
      );

      await useCase.execute({ verifyPageContent: true });
 
      expect(mockAuthService.verifyPageAuthentication).toHaveBeenCalledTimes(1);
    });

    it('should return EXPIRED when cookies valid but page shows login UI', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.AUTHENTICATED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() + 3600000))
      );
      mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
        Result.ok(new BrowserAuthenticationState(true, false))
      );

      const result = await useCase.execute({ verifyPageContent: true });

      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
    });

    it('should return AUTHENTICATED when both cookies AND page authenticated', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.AUTHENTICATED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() + 3600000))
      );
      mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
        Result.ok(new BrowserAuthenticationState(true, true))
      );

      const result = await useCase.execute({ verifyPageContent: true });

      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
    });

    it('should default verifyPageContent to false (backward compatible)', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.AUTHENTICATED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() + 3600000))
      );
      mockAuthService.verifyPageAuthentication = vi.fn();
 
      await useCase.execute();
 
      expect(mockAuthService.verifyPageAuthentication).not.toHaveBeenCalled();
    });

    it('should handle verifyPageAuthentication errors gracefully', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.AUTHENTICATED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() + 3600000))
      );
      mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
        Result.err('Page navigation failed')
      );

      const result = await useCase.execute({ verifyPageContent: true });

      // Should not block on page verification errors, return cookie-based state
      expect(result.isOk()).toBe(true);
      expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
    });
  });

  describe('BDD Scenarios', () => {
    it('Given valid session cookies, When checking auth, Then return AUTHENTICATED', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.AUTHENTICATED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() + 7200000))
      );

      const result = await useCase.execute();

      expect(result.unwrap()).toBe(AuthenticationState.AUTHENTICATED);
    });

    it('Given expired session cookies, When checking auth, Then return EXPIRED', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.EXPIRED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() - 1000))
      );

      const result = await useCase.execute();

      expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
    });

    it('Given no session file, When checking auth, Then return UNKNOWN', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.UNKNOWN)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(null)
      );

      const result = await useCase.execute();

      expect(result.unwrap()).toBe(AuthenticationState.UNKNOWN);
    });

    it('Given valid cookies but page shows login, When verifying page content, Then return EXPIRED', async () => {
      const useCase = new CheckAuthenticationUseCase(
        mockAuthService as unknown as AuthenticationServicePort
      );
      
      mockAuthService.checkSession.mockResolvedValue(
        Result.ok(AuthenticationState.AUTHENTICATED)
      );
      mockAuthService.getSessionExpiry.mockResolvedValue(
        Result.ok(new Date(Date.now() + 3600000))
      );
      mockAuthService.verifyPageAuthentication = vi.fn().mockResolvedValue(
        Result.ok(new BrowserAuthenticationState(true, false))
      );

      const result = await useCase.execute({ verifyPageContent: true });

      expect(result.unwrap()).toBe(AuthenticationState.EXPIRED);
    });
  });
});