/*
 * Copyright (c) 2023 Eclipse Foundation, Inc.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * Contributors:
 *   Olivier Goulet <olivier.goulet@eclipse-foundation.org>
 *
 * SPDX-License-Identifier: EPL-2.0
 */

import { rest } from 'msw';
import { server } from './mocks/server';
import { getProjectCount, getProjectsData, getProjectProposals } from '../eclipsefdn.projects';
import { mockProposals, mockProjects, generateMockProject } from './mocks/projects/mock-data';

const projectsUrl = 'https://projects.eclipse.org/api/projects';
const proposalsUrl = 'https://projects.eclipse.org/api/proposals';

describe('Projects API', () => {
  describe('Projects', () => {
    it('Fetches projects for a specified working group', async () => { 
      const projects = await getProjectsData(projectsUrl + '?working_group=jakarta-ee');
      expect(
        projects.every((project) => 
          project.industry_collaborations.some((collaboration) => collaboration.id === 'jakarta-ee')
        )).toBe(true);
    });

    it('Fetches projects for a specified industry collaboration', async () => { 
      const projects = await getProjectsData(projectsUrl + '?industry_collaboration=jakarta-ee');
      expect(
        projects.every((project) => 
          project.industry_collaborations.some((collaboration) => collaboration.id === 'jakarta-ee')
        )).toBe(true);
    });

    it('Generates a website URL for projects that do not have one', async () => {
      let mockProjectWithoutWebsite = generateMockProject();
      mockProjectWithoutWebsite.website_url = undefined;
      
      let mockProjectBlankWebsite = generateMockProject();
      mockProjectBlankWebsite.website_url = '';

      // Mock a 200 response with the projects without a website URL.
      server.use(
        rest.get(projectsUrl, (_req, res, ctx) => {
          return res(
            ctx.status(200),
            ctx.json([mockProjectWithoutWebsite, mockProjectBlankWebsite])
          )
        })
      );

      const projects = await getProjectsData(projectsUrl);

      expect(projects.every((project) => project.website_url)).toBeTruthy();
    });

    it('Returns null if an invalid url was given when attempting to fetch projects from API', async () => {
      const projects = await getProjectsData('');
      expect(projects).toBeNull();
    });

    it('Throws an error if projects could not be retrieved', async () => {
      // Mock a 500 error.
      server.use(
        rest.get(projectsUrl, (_req, res, ctx) => {
          return res(
            ctx.status(500)
          );
        })
      );

      const projects = getProjectsData(projectsUrl);

      // Expect that the promise gets rejected.
      await expect(projects).rejects.toBeTruthy();
    });

    it('Fetches projects from a static source', async () => {
      const staticUrl = 'https://www.eclipse.org/static/source/projects';

      server.use(
        rest.get(staticUrl, (_req, res, ctx) => {
          return res(
            ctx.status(200), 
            ctx.json(mockProjects)
          );
        })
      );

      const projects = await getProjectsData(staticUrl, true);
      
      expect(projects).toHaveLength(mockProjects.length);
    });

    it('Returns null if attempting to fetch static sourced projects with an invalid url', async () => {
      const projects = await getProjectsData('', true);
      expect(projects).toBeNull();
    });

    it('Rejects promise if static sourced projects could not be retrieved', async () => {
      const staticUrl = 'https://www.eclipse.org/static/source/projects';

      server.use(
        rest.get(staticUrl, (_req, res, ctx) => {
          return res(
            ctx.status(500)
          );
        })
      );

      const projects = getProjectsData(staticUrl, true);

      // Expect that the promise gets rejected.
      expect(projects).rejects.toBeTruthy();
    });

    it('Generates a website URL for projects from static sources that do not have one', async () => {
      let mockProjectWithoutWebsite = generateMockProject();
      mockProjectWithoutWebsite.website_url = undefined;
      
      let mockProjectBlankWebsite = generateMockProject();
      mockProjectBlankWebsite.website_url = '';

      // Mock a 200 response with the projects without a website URL.
      server.use(
        rest.get(projectsUrl, (_req, res, ctx) => {
          return res(
            ctx.status(200),
            ctx.json([mockProjectWithoutWebsite, mockProjectBlankWebsite])
          )
        })
      );

      const projects = await getProjectsData(projectsUrl, true);

      expect(projects.every((project) => project.website_url)).toBeTruthy();
    });

    it('Counts all projects from the Eclipse Foundation by default', async () => {
      const [count, error] = await getProjectCount();

      expect(count).toBe(mockProjects.length);
      expect(error).toBeNull();
    });

    it('Counts all projects from a specified industry collaboration', async () => {
      const [count, error] = await getProjectCount('jakarta-ee');

      const jakartaEEProjects = mockProjects.filter((project) => project.industry_collaborations.some((collaboration) => collaboration.id === 'jakarta-ee'));

      expect(count).toBe(jakartaEEProjects.length);
      expect(error).toBeNull();
    });

    it('Logs an error message if project count could not be retrieved', async () => {
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();

      server.use(
        rest.get(projectsUrl, (_req, res, ctx) => {
          return res(
            ctx.status(500)
          );
        })
      );

      const [count, error] = await getProjectCount();

      expect(count).toBe(null);
      expect(error).not.toBeNull();
      expect(consoleSpy).toHaveBeenCalled();
    });
  });

  describe('Proposals', () => {
    it('Fetches all proposals', async () => {
      const [result, error] = await getProjectProposals();

      expect(result).not.toBeNull();
      expect(error).toBeNull();
      expect(result).toHaveLength(mockProposals.length);
      expect(result[0]).toHaveProperty('project_id');
    });

    it('Fetches proposals for a specific industry collaboration', async () => {
      const [result, error] = await getProjectProposals('jakarta-ee');

      const expectedProposals = mockProposals.filter((proposal) => proposal.industry_collaboration.id === 'jakarta-ee');

      expect(result).not.toBeNull();
      expect(error).toBeNull();
      expect(result).toHaveLength(expectedProposals.length);
      expect(result[0]).toHaveProperty('project_id');
    });

    it('Resolves empty array when no proposals found', async () => {
      const [result] = await getProjectProposals('eclipse-software-defined-vehicle');

      expect(result).toEqual([]);
    });

    it('Logs an error message if proposals could not be retrieved', async () => {
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();

      server.use(
        rest.get(proposalsUrl, (_req, res, ctx) => {
          return res(
            ctx.status(500)
          );
        })
      );

      const [result, error] = await getProjectProposals();

      expect(result).toBe(null);
      expect(error).not.toBeNull();
      expect(consoleSpy).toHaveBeenCalled();
    });
  });
});
