/**
 * @vitest-environment node
 */

import { vi } from 'vitest';
import {
  asProjectKey,
  asRunId,
  asCommitOid,
  asBranchName,
  asGraphQLNodeId,
} from '../../types/branded';
import type { IAnalysisRunRepository } from '../../domain/aggregates/analysis-run/analysis-run.repository';
import type { AnalysisRun } from '../../domain/aggregates/analysis-run/analysis-run.aggregate';
import type { Logger } from '../../utils/logging/logger';
import { IssueCount } from '../../domain/value-objects/issue-count';

// Create mock logger
const mockLogger = {
  debug: vi.fn(),
  info: vi.fn(),
  warn: vi.fn(),
  error: vi.fn(),
};

// Mock modules before importing the implementation
vi.mock('../../utils/logging/logger', () => ({
  createLogger: vi.fn(() => mockLogger),
}));

// Mock the repository and factory
const mockFindMostRecent = vi.fn();
const mockAnalysisRunRepository = {
  findMostRecent: mockFindMostRecent,
} as unknown as IAnalysisRunRepository;

const mockCreateAnalysisRunRepository = vi.fn(() => mockAnalysisRunRepository);
const mockRepositoryFactory = vi.fn(() => ({
  createAnalysisRunRepository: mockCreateAnalysisRunRepository,
}));

vi.mock('../../infrastructure/factories/repository.factory', () => ({
  RepositoryFactory: mockRepositoryFactory,
}));

// Mock the DeepSource client
const mockGetRecentRunIssues = vi.fn();
const mockDeepSourceClient = vi.fn(() => ({
  getRecentRunIssues: mockGetRecentRunIssues,
}));

vi.mock('../../deepsource.js', () => ({
  DeepSourceClient: mockDeepSourceClient,
  ReportType: {
    OWASP_TOP_10: 'OWASP_TOP_10',
    SANS_TOP_25: 'SANS_TOP_25',
    MISRA_C: 'MISRA_C',
    CODE_COVERAGE: 'CODE_COVERAGE',
    CODE_HEALTH_TREND: 'CODE_HEALTH_TREND',
    ISSUE_DISTRIBUTION: 'ISSUE_DISTRIBUTION',
    ISSUES_PREVENTED: 'ISSUES_PREVENTED',
    ISSUES_AUTOFIXED: 'ISSUES_AUTOFIXED',
  },
  ReportStatus: {
    PASSING: 'PASSING',
    FAILING: 'FAILING',
    NOOP: 'NOOP',
  },
}));

// Import the modules under test AFTER mocking
const { handleDeepsourceRecentRunIssues, createRecentRunIssuesHandlerWithRepo } = await import(
  '../../handlers/recent-run-issues'
);

describe('Recent Run Issues Handler', () => {
  // Environment backup
  let originalEnv: Record<string, string | undefined>;

  beforeEach(() => {
    // Backup environment
    originalEnv = { ...process.env };
    process.env.DEEPSOURCE_API_KEY = 'test-api-key';

    // Reset mocks
    vi.clearAllMocks();
  });

  afterEach(() => {
    // Restore environment
    process.env = originalEnv;
  });

  describe('createRecentRunIssuesHandlerWithRepo', () => {
    it('should create a handler that finds recent run and fetches issues', async () => {
      // Mock domain analysis run
      const projectKey = asProjectKey('test-project');
      const branchName = asBranchName('main');
      const mockRun = {
        runId: asRunId('run-123'),
        projectKey,
        repositoryId: asGraphQLNodeId('repo-1'),
        commitInfo: {
          oid: asCommitOid('commit-abc'),
          branch: branchName,
          baseOid: asCommitOid('base-def'),
        },
        status: 'SUCCESS' as const,
        timestamps: {
          createdAt: new Date('2023-01-01T00:00:00Z'),
          startedAt: new Date('2023-01-01T00:01:00Z'),
          finishedAt: new Date('2023-01-01T00:05:00Z'),
        },
        summary: {
          totalIntroduced: IssueCount.create(3),
          totalResolved: IssueCount.create(1),
          totalSuppressed: IssueCount.create(0),
          byAnalyzer: [
            {
              analyzerShortcode: 'eslint',
              introduced: IssueCount.create(2),
              resolved: IssueCount.create(1),
              suppressed: IssueCount.create(0),
            },
          ],
          byCategory: [
            {
              category: 'BUG_RISK',
              introduced: IssueCount.create(3),
              resolved: IssueCount.create(1),
              suppressed: IssueCount.create(0),
            },
          ],
        },
      } as AnalysisRun;

      // Mock client response for issues
      const mockClientResponse = {
        run: {
          id: 'run-graphql-id-1',
          runUid: 'run-123',
          commitOid: 'commit-abc',
          branchName: 'main',
          baseOid: 'base-def',
          status: 'SUCCESS',
          createdAt: '2023-01-01T00:00:00Z',
          updatedAt: '2023-01-01T00:05:00Z',
          finishedAt: '2023-01-01T00:05:00Z',
          summary: {
            occurrencesIntroduced: 3,
            occurrencesResolved: 1,
            occurrencesSuppressed: 0,
          },
          repository: {
            name: 'test-repo',
            id: 'repo-graphql-id-1',
          },
        },
        items: [
          {
            id: 'issue-1',
            title: 'Test issue',
            shortcode: 'PY-E1101',
            category: 'BUG',
            severity: 'MAJOR',
            status: 'OPEN',
            issue_text: 'Instance of class has no attribute',
            file_path: 'src/test.py',
            line_number: 42,
            tags: ['python', 'bug-risk'],
          },
        ],
        pageInfo: {
          hasNextPage: false,
          hasPreviousPage: false,
          startCursor: null,
          endCursor: null,
        },
        totalCount: 1,
      };

      // Set up the mocks
      mockFindMostRecent.mockResolvedValue(mockRun);
      mockGetRecentRunIssues.mockResolvedValue(mockClientResponse);

      const mockClient = {
        getRecentRunIssues: mockGetRecentRunIssues,
      };

      const handler = createRecentRunIssuesHandlerWithRepo({
        analysisRunRepository: mockAnalysisRunRepository,
        client: mockClient,
        logger: mockLogger as unknown as Logger,
      });

      // Call the handler
      const result = await handler({
        projectKey: 'test-project',
        branchName: 'main',
      });

      // Verify repository was used correctly
      expect(mockFindMostRecent).toHaveBeenCalledWith(projectKey, branchName);

      // Verify client was used correctly
      expect(mockGetRecentRunIssues).toHaveBeenCalledWith('test-project', 'main');

      // Verify the response structure
      const parsedContent = JSON.parse(result.content[0].text);
      expect(parsedContent.run.id).toBe('run-123');
      expect(parsedContent.run.runUid).toBe('run-123');
      expect(parsedContent.run.status).toBe('SUCCESS');
      expect(parsedContent.run.commitOid).toBe('commit-abc');
      expect(parsedContent.run.branchName).toBe('main');
      expect(parsedContent.run.summary.occurrencesIntroduced).toBe(3);
      expect(parsedContent.run.summary.occurrencesResolved).toBe(1);
      expect(parsedContent.run.summary.occurrencesSuppressed).toBe(0);
      expect(parsedContent.issues).toHaveLength(1);
      expect(parsedContent.issues[0].id).toBe('issue-1');
      expect(parsedContent.issues[0].title).toBe('Test issue');
      expect(parsedContent.totalCount).toBe(1);
      expect(parsedContent.usage_examples).toBeDefined();
    });

    it('should handle case when no recent run is found', async () => {
      // Set up the mock to return null
      mockFindMostRecent.mockResolvedValue(null);

      const mockClient = {
        getRecentRunIssues: mockGetRecentRunIssues,
      };

      const handler = createRecentRunIssuesHandlerWithRepo({
        analysisRunRepository: mockAnalysisRunRepository,
        client: mockClient,
        logger: mockLogger as unknown as Logger,
      });

      // Call the handler
      const result = await handler({
        projectKey: 'test-project',
        branchName: 'feature-branch',
      });

      // Verify error response
      expect(result.isError).toBe(true);
      const parsedContent = JSON.parse(result.content[0].text);
      expect(parsedContent.error).toContain(
        'No recent analysis run found for branch "feature-branch" in project "test-project"'
      );
      expect(parsedContent.details).toBe('Failed to retrieve recent run issues');
    });

    it('should handle repository errors gracefully', async () => {
      // Set up the mock to throw an error
      const testError = new Error('Repository connection failed');
      mockFindMostRecent.mockRejectedValue(testError);

      const mockClient = {
        getRecentRunIssues: mockGetRecentRunIssues,
      };

      const handler = createRecentRunIssuesHandlerWithRepo({
        analysisRunRepository: mockAnalysisRunRepository,
        client: mockClient,
        logger: mockLogger as unknown as Logger,
      });

      // Call the handler
      const result = await handler({
        projectKey: 'test-project',
        branchName: 'main',
      });

      // Verify error was logged using injected logger
      expect(mockLogger.error).toHaveBeenCalledWith(
        'Error in handleRecentRunIssues',
        expect.any(Object)
      );

      // Verify error response
      expect(result.isError).toBe(true);
      const parsedContent = JSON.parse(result.content[0].text);
      expect(parsedContent).toEqual({
        error: 'Repository connection failed',
        details: 'Failed to retrieve recent run issues',
      });
    });

    it('should handle client errors gracefully', async () => {
      // Mock successful repository call but failing client call
      const mockRun = {
        runId: asRunId('run-123'),
        projectKey: asProjectKey('test-project'),
        repositoryId: asGraphQLNodeId('repo-1'),
        commitInfo: {
          oid: asCommitOid('commit-abc'),
          branch: asBranchName('main'),
          baseOid: asCommitOid('base-def'),
        },
        status: 'SUCCESS' as const,
        timestamps: {
          createdAt: new Date('2023-01-01T00:00:00Z'),
        },
        summary: {
          totalIntroduced: IssueCount.create(0),
          totalResolved: IssueCount.create(0),
          totalSuppressed: IssueCount.create(0),
          byAnalyzer: [],
          byCategory: [],
        },
      } as AnalysisRun;

      mockFindMostRecent.mockResolvedValue(mockRun);

      const testError = new Error('Client API failed');
      mockGetRecentRunIssues.mockRejectedValue(testError);

      const mockClient = {
        getRecentRunIssues: mockGetRecentRunIssues,
      };

      const handler = createRecentRunIssuesHandlerWithRepo({
        analysisRunRepository: mockAnalysisRunRepository,
        client: mockClient,
        logger: mockLogger as unknown as Logger,
      });

      // Call the handler
      const result = await handler({
        projectKey: 'test-project',
        branchName: 'main',
      });

      // Verify error response
      expect(result.isError).toBe(true);
      const parsedContent = JSON.parse(result.content[0].text);
      expect(parsedContent.error).toBe('Client API failed');
    });

    it('should warn when domain run and client run mismatch', async () => {
      // Mock domain analysis run
      const mockRun = {
        runId: asRunId('run-123'),
        projectKey: asProjectKey('test-project'),
        repositoryId: asGraphQLNodeId('repo-1'),
        commitInfo: {
          oid: asCommitOid('commit-abc'),
          branch: asBranchName('main'),
          baseOid: asCommitOid('base-def'),
        },
        status: 'SUCCESS' as const,
        timestamps: {
          createdAt: new Date('2023-01-01T00:00:00Z'),
        },
        summary: {
          totalIntroduced: IssueCount.create(1),
          totalResolved: IssueCount.create(0),
          totalSuppressed: IssueCount.create(0),
          byAnalyzer: [],
          byCategory: [],
        },
      } as AnalysisRun;

      // Mock client response with different run ID
      const mockClientResponse = {
        run: {
          id: 'run-graphql-id-different',
          runUid: 'run-456', // Different from domain run
          commitOid: 'commit-different',
          branchName: 'main',
          status: 'SUCCESS',
          createdAt: '2023-01-01T00:00:00Z',
          summary: {
            occurrencesIntroduced: 1,
            occurrencesResolved: 0,
            occurrencesSuppressed: 0,
          },
          repository: {
            name: 'test-repo',
            id: 'repo-graphql-id-1',
          },
        },
        items: [],
        pageInfo: {
          hasNextPage: false,
          hasPreviousPage: false,
        },
        totalCount: 0,
      };

      mockFindMostRecent.mockResolvedValue(mockRun);
      mockGetRecentRunIssues.mockResolvedValue(mockClientResponse);

      const mockClient = {
        getRecentRunIssues: mockGetRecentRunIssues,
      };

      const handler = createRecentRunIssuesHandlerWithRepo({
        analysisRunRepository: mockAnalysisRunRepository,
        client: mockClient,
        logger: mockLogger as unknown as Logger,
      });

      // Call the handler
      const result = await handler({
        projectKey: 'test-project',
        branchName: 'main',
      });

      // Verify warning was logged
      expect(mockLogger.warn).toHaveBeenCalledWith('Mismatch between domain run and client run', {
        domainRunId: 'run-123',
        clientRunUid: 'run-456',
      });

      // Should still return successful response using domain data
      expect(result.isError).toBeUndefined();
      const parsedContent = JSON.parse(result.content[0].text);
      expect(parsedContent.run.id).toBe('run-123'); // Uses domain data
      expect(parsedContent.run.runUid).toBe('run-123'); // Uses domain data
    });

    it('should handle run with missing optional data', async () => {
      // Mock domain analysis run with minimal data
      const mockRun = {
        runId: asRunId('run-minimal'),
        projectKey: asProjectKey('test-project'),
        repositoryId: asGraphQLNodeId('repo-1'),
        commitInfo: {
          oid: asCommitOid('commit-minimal'),
          branch: asBranchName('main'),
          baseOid: asCommitOid('base-minimal'),
        },
        status: 'PENDING' as const,
        timestamps: {
          createdAt: new Date('2023-01-01T00:00:00Z'),
        },
        summary: {
          totalIntroduced: IssueCount.create(0),
          totalResolved: IssueCount.create(0),
          totalSuppressed: IssueCount.create(0),
          byAnalyzer: [],
          byCategory: [],
        },
      } as AnalysisRun;

      const mockClientResponse = {
        run: {
          id: 'run-graphql-id-minimal',
          runUid: 'run-minimal',
          commitOid: 'commit-minimal',
          branchName: 'main',
          status: 'PENDING',
          createdAt: '2023-01-01T00:00:00Z',
          summary: {
            occurrencesIntroduced: 0,
            occurrencesResolved: 0,
            occurrencesSuppressed: 0,
          },
          repository: {
            name: 'test-repo',
            id: 'repo-graphql-id-1',
          },
        },
        items: [],
        pageInfo: {
          hasNextPage: false,
          hasPreviousPage: false,
        },
        totalCount: 0,
      };

      mockFindMostRecent.mockResolvedValue(mockRun);
      mockGetRecentRunIssues.mockResolvedValue(mockClientResponse);

      const mockClient = {
        getRecentRunIssues: mockGetRecentRunIssues,
      };

      const handler = createRecentRunIssuesHandlerWithRepo({
        analysisRunRepository: mockAnalysisRunRepository,
        client: mockClient,
        logger: mockLogger as unknown as Logger,
      });

      // Call the handler
      const result = await handler({
        projectKey: 'test-project',
        branchName: 'main',
      });

      // Verify the response handles missing optional data gracefully
      const parsedContent = JSON.parse(result.content[0].text);
      expect(parsedContent.run.status).toBe('PENDING');
      expect(parsedContent.run.finishedAt).toBeUndefined();
      expect(parsedContent.issues).toEqual([]);
      expect(parsedContent.totalCount).toBe(0);
    });
  });

  describe('handleDeepsourceRecentRunIssues', () => {
    it('should throw an error if DEEPSOURCE_API_KEY is not set', async () => {
      // Unset API key
      delete process.env.DEEPSOURCE_API_KEY;

      // Call the handler and expect it to throw
      await expect(
        handleDeepsourceRecentRunIssues({
          projectKey: 'test-project',
          branchName: 'main',
        })
      ).rejects.toThrow('Configuration error: DeepSource API key is required but not configured');
    });

    it('should return recent run issues successfully', async () => {
      // Mock domain analysis run
      const mockRun = {
        runId: asRunId('run-success'),
        projectKey: asProjectKey('test-project'),
        repositoryId: asGraphQLNodeId('repo-1'),
        commitInfo: {
          oid: asCommitOid('commit-success'),
          branch: asBranchName('main'),
          baseOid: asCommitOid('base-success'),
        },
        status: 'SUCCESS' as const,
        timestamps: {
          createdAt: new Date('2023-01-01T00:00:00Z'),
          startedAt: new Date('2023-01-01T00:01:00Z'),
          finishedAt: new Date('2023-01-01T00:05:00Z'),
        },
        summary: {
          totalIntroduced: IssueCount.create(2),
          totalResolved: IssueCount.create(1),
          totalSuppressed: IssueCount.create(0),
          byAnalyzer: [],
          byCategory: [],
        },
      } as AnalysisRun;

      const mockClientResponse = {
        run: {
          id: 'run-graphql-id-success',
          runUid: 'run-success',
          commitOid: 'commit-success',
          branchName: 'main',
          status: 'SUCCESS',
          createdAt: '2023-01-01T00:00:00Z',
          summary: {
            occurrencesIntroduced: 2,
            occurrencesResolved: 1,
            occurrencesSuppressed: 0,
          },
          repository: {
            name: 'test-repo',
            id: 'repo-graphql-id-1',
          },
        },
        items: [
          {
            id: 'issue-success',
            title: 'Success issue',
            shortcode: 'JS-0001',
            category: 'BUG',
            severity: 'MINOR',
            status: 'OPEN',
            issue_text: 'Test issue',
            file_path: 'src/success.js',
            line_number: 10,
            tags: ['javascript'],
          },
        ],
        pageInfo: {
          hasNextPage: false,
          hasPreviousPage: false,
        },
        totalCount: 1,
      };

      // Set up the mocks
      mockFindMostRecent.mockResolvedValue(mockRun);
      mockGetRecentRunIssues.mockResolvedValue(mockClientResponse);

      // Call the handler
      const result = await handleDeepsourceRecentRunIssues({
        projectKey: 'test-project',
        branchName: 'main',
      });

      // Verify factory was created with the API key
      expect(mockRepositoryFactory).toHaveBeenCalledWith({ apiKey: 'test-api-key' });

      // Verify createAnalysisRunRepository was called
      expect(mockCreateAnalysisRunRepository).toHaveBeenCalled();

      // Verify DeepSourceClient was created with the API key
      expect(mockDeepSourceClient).toHaveBeenCalledWith('test-api-key');

      // Verify logging behavior
      expect(mockLogger.info).toHaveBeenCalledWith(
        'Fetching recent run from repository and issues from client',
        expect.any(Object)
      );

      // Verify the response structure
      expect(result).toHaveProperty('content');
      expect(result.content).toHaveLength(1);
      expect(result.content[0]).toHaveProperty('type', 'text');

      // Parse and verify the content
      const parsedContent = JSON.parse(result.content[0].text);
      expect(parsedContent.run.id).toBe('run-success');
      expect(parsedContent.run.status).toBe('SUCCESS');
      expect(parsedContent.issues).toHaveLength(1);
      expect(parsedContent.issues[0].id).toBe('issue-success');
    });

    it('should throw error when repository fails', async () => {
      // Set up the mock to throw an error
      const testError = new Error('Repository connection failed');
      mockFindMostRecent.mockRejectedValue(testError);

      // Call the handler and expect it to throw
      await expect(
        handleDeepsourceRecentRunIssues({
          projectKey: 'test-project',
          branchName: 'main',
        })
      ).rejects.toThrow('Repository connection failed');
    });

    it('should throw error when no recent run found', async () => {
      // Set up the mock to return null
      mockFindMostRecent.mockResolvedValue(null);

      // Call the handler and expect it to throw
      await expect(
        handleDeepsourceRecentRunIssues({
          projectKey: 'test-project',
          branchName: 'feature-branch',
        })
      ).rejects.toThrow(
        'No recent analysis run found for branch "feature-branch" in project "test-project"'
      );
    });

    it('should handle non-Error type exceptions', async () => {
      // Set up the mock to throw a non-Error value
      mockFindMostRecent.mockRejectedValue('Just a string error');

      // Call the handler and expect it to throw
      await expect(
        handleDeepsourceRecentRunIssues({
          projectKey: 'test-project',
          branchName: 'main',
        })
      ).rejects.toThrow('Unknown error');
    });
  });
});
