/**
 * @vitest-environment jsdom
 */

import React from 'react';
import { renderToString } from 'react-dom/server';
import { expect, it, describe, beforeEach, afterEach, vi } from 'vitest';
import type { RouteComponent as IRouteComponent } from '../src/types';
import RouteWrapper from '../src/RouteWrapper';
import { AppContextProvider } from '../src/AppContext';
import {
  filterMatchesToLoad,
  createRouteElements,
  RouteComponent,
  loadRouteModules,
  loadRoutesData,
  getRoutesConfig,
  getRoutesPath,
} from '../src/routes.js';

describe('routes', () => {
  let windowSpy;
  beforeEach(() => {
    windowSpy = vi.spyOn(global, 'window', 'get');
  });

  afterEach(() => {
    windowSpy.mockRestore();
  });

  Object.defineProperty(window, 'open', open);
  const homeItem = {
    default: () => <></>,
    pageConfig: () => ({ title: 'home' }),
    dataLoader: async () => ({ type: 'getData' }),
    serverDataLoader: async () => ({ type: 'getServerData' }),
    staticDataLoader: async () => ({ type: 'getStaticData' }),
  };
  const aboutItem = {
    default: () => <></>,
    pageConfig: () => ({ title: 'about' }),
  };
  const routeModules = [
    {
      id: 'home',
      load: async () => {
        return homeItem as IRouteComponent;
      },
    },
    {
      id: 'about',
      load: async () => {
        return aboutItem as IRouteComponent;
      },
    },
  ];

  it('route Component', () => {
    const domstring = renderToString(
      // @ts-ignore
      <AppContextProvider value={{
        routeModules: {
          home: {
            default: () => <div>home</div>,
          },
        },
      }}
      >
        <RouteComponent id="home" />
      </AppContextProvider>,
    );
    expect(domstring).toBe('<div>home</div>');
  });

  it('route error', () => {
    const currentEnv = process.env.NODE_ENV;
    process.env.NODE_ENV = 'development';
    expect(() => renderToString(
      // @ts-ignore
      <AppContextProvider value={{ routeModules: {} }}>
        <RouteComponent id="home" />
      </AppContextProvider>,
    )).toThrow();
    process.env.NODE_ENV = currentEnv;
  });

  it('load route modules', async () => {
    windowSpy.mockImplementation(() => ({}));
    const routeModule = await loadRouteModules(routeModules, { home: homeItem });
    expect(routeModule).toStrictEqual({
      home: homeItem,
      about: aboutItem,
    });
  });

  it('load error module', async () => {
    const routeModule = await loadRouteModules([{
      id: 'error',
      // @ts-ignore
      load: async () => {
        throw new Error('err');
        return {};
      },
    }], {});
    expect(routeModule).toStrictEqual({
      error: undefined,
    });
  });

  it('load route data for SSG', async () => {
    const routeModule = await loadRouteModules(routeModules);
    const routesDataSSG = await loadRoutesData(
      // @ts-ignore
      [{ route: routeModules[0] }],
      {},
      routeModule,
      {
        renderMode: 'SSG',
      },
    );

    expect(routesDataSSG).toStrictEqual({
      home: {
        type: 'getStaticData',
      },
    });
  });

  it('load route data for SSR', async () => {
    const routeModule = await loadRouteModules(routeModules);
    const routesDataSSR = await loadRoutesData(
      // @ts-ignore
      [{ route: routeModules[0] }],
      {},
      routeModule,
      {
        renderMode: 'SSR',
      },
    );

    expect(routesDataSSR).toStrictEqual({
      home: {
        type: 'getServerData',
      },
    });
  });

  it('load route data for CSR', async () => {
    const routeModule = await loadRouteModules(routeModules);
    const routesDataCSR = await loadRoutesData(
      // @ts-ignore
      [{ route: routeModules[0] }],
      {},
      routeModule,
      {
        renderMode: 'CSR',
      },
    );

    expect(routesDataCSR).toStrictEqual({
      home: {
        type: 'getData',
      },
    });
  });

  it('load data from __ICE_DATA_LOADER__', async () => {
    windowSpy.mockImplementation(() => ({
      __ICE_DATA_LOADER__: {
        getData: async (id) => ({ id: `${id}_data` }),
      },
    }));
    const routesData = await loadRoutesData(
      // @ts-ignore
      [{ route: routeModules[0] }],
      {},
      {},
      { renderMode: 'SSG' },
    );
    expect(routesData).toStrictEqual({
      home: {
        id: 'home_data',
      },
    });
  });

  it('get routes config', async () => {
    const routeModule = await loadRouteModules(routeModules);
    const routesConfig = getRoutesConfig(
      // @ts-ignore
      [{ route: routeModules[0] }],
      { home: {} },
      routeModule,
    );
    expect(routesConfig).toStrictEqual({
      home: {
        title: 'home',
      },
    });
  });

  it('get routes config when failed get route module', async () => {
    const routesConfig = getRoutesConfig(
      // @ts-ignore
      [{ route: routeModules[0] }],
      { home: {} },
      {},
    );
    expect(routesConfig).toStrictEqual({
      home: {},
    });
  });

  it('create route element', () => {
    const routeElement = createRouteElements([{
      path: '/',
      id: 'home',
      componentName: 'home',
    }]);
    expect(routeElement).toEqual([{
      componentName: 'home',
      element: (
        <RouteWrapper id="home">
          <RouteComponent
            id="home"
          />
        </RouteWrapper>
      ),
      id: 'home',
      path: '/',
    }]);
  });

  it('create route with children', () => {
    const routeElement = createRouteElements([{
      path: '/',
      id: 'home',
      componentName: 'home',
      children: [{
        path: '/about',
        id: 'about',
        componentName: 'about',
      }],
    }]);
    expect(routeElement).toEqual([{
      componentName: 'home',
      element: (
        <RouteWrapper id="home">
          <RouteComponent
            id="home"
          />
        </RouteWrapper>
      ),
      children: [{
        componentName: 'about',
        element: (
          <RouteWrapper id="about">
            <RouteComponent
              id="about"
            />
          </RouteWrapper>
        ),
        id: 'about',
        path: '/about',
      }],
      id: 'home',
      path: '/',
    }]);
  });

  it('filter new matches', () => {
    const oldMatches = [
      {
        pathname: '/',
        route: {
          id: '/page/layout',
        },
      },
      {
        pathname: '/',
        route: {
          id: '/page/home',
        },
      },
    ];

    const newMatches = [
      {
        pathname: '/',
        route: {
          id: '/page/layout',
        },
      },
      {
        pathname: '/about',
        route: {
          id: '/page/about',
        },
      },
    ];

    // @ts-ignore
    const matches = filterMatchesToLoad(oldMatches, newMatches);

    expect(
      matches,
    ).toEqual([{
      pathname: '/about',
      route: {
        id: '/page/about',
      },
    }]);
  });

  it('filter matches with path changed', () => {
    const oldMatches = [
      {
        pathname: '/users/123',
        route: {
          id: '/users/123',
        },
      },
    ];

    const newMatches = [
      {
        pathname: '/users/456',
        route: {
          id: '/users/456',
        },
      },
    ];

    // @ts-ignore
    const matches = filterMatchesToLoad(oldMatches, newMatches);

    expect(
      matches,
    ).toEqual([
      {
        pathname: '/users/456',
        route: {
          id: '/users/456',
        },
      },
    ]);
  });

  it('get routes flatten path', () => {
    const routes = [
      {
        path: 'error',
        id: 'home',
      },
      {
        id: 'index',
      },
    ];
    // @ts-ignore
    const result = getRoutesPath(routes);
    expect(result).toEqual(['/error', '/']);
  });

  it('get flatten path of nested routes', () => {
    const routes = [
      {
        id: 'layout',
        children: [
          {
            id: 'home',
            path: 'home',
          },
          {
            id: 'layout',
            path: 'dashboard',
            children: [
              {
                id: 'about',
                path: 'about',
              },
              {
                id: 'blog',
                path: 'blog',
              },
            ],
          },
        ],
      },
      {
        id: 'index',
        componentName: 'index',
      },
    ];
    // @ts-ignore
    const result = getRoutesPath(routes);
    expect(result).toEqual(['/home', '/dashboard/about', '/dashboard/blog', '/']);
  });
});
