import * as path from 'path';
import { fileURLToPath } from 'url';
import { expect, it, describe } from 'vitest';
import { transformManifestKeys, parseManifest, getAppWorkerUrl, rewriteAppWorker, getMultipleManifest } from '../src/manifestHelpers';
import * as mockServer from './mockServer.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

describe('get app work url', () => {
  it('app worker config as remote url', () => {
    const manifest = {
      appWorker: {
        url: 'http://remote/app-worker.js',
      },
    };
    const appWorkerPath = getAppWorkerUrl(manifest, __dirname);
    expect(appWorkerPath).toBeUndefined();
  });

  it('app worker config which do not exist', () => {
    const manifest = {
      appWorker: {
        url: 'app-worker.js',
      },
    };
    const appWorkerPath = getAppWorkerUrl(manifest, __dirname);
    expect(appWorkerPath).toBeUndefined();
  });

  it('app worker config which exists', () => {
    const manifest = {
      appWorker: {
        url: 'pha-work.js',
      },
    };
    const appWorkerPath = getAppWorkerUrl(manifest, __dirname);
    expect(appWorkerPath).toBe(path.join(__dirname, 'pha-work.js'));
  });

  it('found default app worker', () => {
    const manifest = {};
    const appWorkerPath = getAppWorkerUrl(manifest, __dirname);
    expect(appWorkerPath).toBe(path.join(__dirname, 'app-worker.ts'));
  });
});

describe('rewrite app worker url', () => {
  it('over write appWorker.url', () => {
    expect(rewriteAppWorker({
      appWorker: {
        url: 'pha-worker.js',
        source: 'test',
      },
    })).toMatchObject({
      appWorker: {
        url: 'app-worker.js',
        source: 'test',
      },
    });
  });

  it('config appWorker', () => {
    expect(rewriteAppWorker({})).toMatchObject({
      appWorker: {
        url: 'app-worker.js',
      },
    });
  });
});


describe('transform config keys', () => {
  it('should transform decamelize keys fields', () => {
    const manifestJSON = transformManifestKeys(
      {
        offlineResources: ['//g.alicdn.com/.*'],
        name: 'name',
        pages: [
          {
            pageHeader: {
              includedSafeArea: true,
            },
            downgradeUrl: 'http://www.taobao.com',
          },
        ],
      },
      { isRoot: true },
    );
    expect(manifestJSON.name).toStrictEqual('name');
    expect(manifestJSON?.pages![0].downgrade_url).toStrictEqual('http://www.taobao.com');
    expect(manifestJSON.offline_resources).toStrictEqual(['//g.alicdn.com/.*']);
    expect(manifestJSON?.pages![0].tab_header).toStrictEqual({ included_safe_area: true });
  });

  it('should transform dataPrefetch to data_prefetch', () => {
    const manifestJSON = transformManifestKeys(
      {
        dataPrefetch: [
          {
            api: '/a.com',
            data: {
              id: 123,
              taskId: 233,
              cId: {
                dId: true,
              },
            },
            header: {
              taskId: 455,
            },
            extHeaders: {
              id: 123,
              test_id: 234,
            },
            dataType: 'json',
            appKey: '12345',
            LoginRequest: true,
          },
        ],
      },
      { isRoot: true },
    );
    expect(manifestJSON?.data_prefetch?.length).toBe(1);
    expect(manifestJSON?.data_prefetch![0].data).toMatchObject({
      id: 123,
      taskId: 233,
      cId: {
        dId: true,
      },
    });
    expect(manifestJSON?.data_prefetch![0].header).toMatchObject({
      taskId: 455,
    });
    expect(manifestJSON?.data_prefetch![0].ext_headers).toMatchObject({ id: 123, test_id: 234 });
    expect(manifestJSON?.data_prefetch![0].dataType).toBe('json');
    expect(manifestJSON?.data_prefetch![0].appKey).toBe('12345');
    expect(manifestJSON?.data_prefetch![0].LoginRequest).toBe(true);
    expect(manifestJSON?.data_prefetch![0].prefetch_key).toBe('mtop');
  });

  it('should transform tabBar to tab_bar', () => {
    const manifestJSON = transformManifestKeys(
      {
        tabBar: {
          textColor: '',
          selectedColor: '',
          backgroundColor: '',
          items: [
            {
              path: 'tab1',
              name: '主会场',
              icon: '',
              activeIcon: '',
            },
            {
              // transform text to name
              text: 'text-name',
              icon: '',
              activeIcon: '',
            },
          ],
        },
      },
      { isRoot: true },
    );

    expect(manifestJSON.tab_bar).toBeTruthy();
    expect(manifestJSON?.tab_bar?.items![0]).toMatchObject({ path: 'tab1', name: '主会场', icon: '', active_icon: '' });
  });

  it('should transform pages keys', () => {
    const manifestJSON = transformManifestKeys(
      {
        pages: [
          {
            path: '/',
            name: 'home',
            source: 'pages/Home/index',
            dataPrefetch: [
              {
                url: '/a.com',
                data: {
                  id: 123,
                },
              },
            ],
          },
          {
            path: '/home1',
            name: 'home1',
            source: 'pages/Home1/index',
          },
        ],
      },
      { isRoot: true },
    );
    expect(manifestJSON?.pages?.length).toBe(2);
    expect(manifestJSON?.pages![0].data_prefetch).toMatchObject([
      {
        url: '/a.com',
        data: {
          id: 123,
        },
        header: {},
        prefetch_key: 'mtop',
      },
    ]);
  });

  it('should not filter whitelist fields', () => {
    const manifestJSON = transformManifestKeys(
      {
        a: 123,
      },
      { isRoot: false },
    );

    expect(manifestJSON).toMatchObject({ a: 123 });
  });

  it('should transform enableExpiredManifest', () => {
    const manifestJSON = transformManifestKeys(
      {
        enableExpiredManifest: true,
      },
      { isRoot: false },
    );

    expect(manifestJSON).toMatchObject({ enable_expired_manifest: true });
  });

  it('should filter unknown fields in root', () => {
    const manifestJSON = transformManifestKeys(
      {
        a: 123,
      },
      { isRoot: true },
    );

    expect(manifestJSON).toMatchObject({});
  });

  it('should not transform requestHeaders', () => {
    const manifestJSON = transformManifestKeys(
      {
        requestHeaders: {
          'U-Tag': '${storage.uTag}',
        },
      },
      { isRoot: false },
    );
    expect(manifestJSON).toMatchObject({ request_headers: { 'U-Tag': '${storage.uTag}' } });
  });
});

describe('parse manifest', async () => {
  const options = {
    publicPath: 'https://cdn-path.com/',
    urlPrefix: 'https://url-prefix.com/',
    routesConfig: (await import(path.join(__dirname, './mockConfig.mjs')))?.default,
    excuteServerEntry: async () => mockServer,
    routeManifest: path.join(__dirname, './route-manifest.json'),
  };

  it('should add urlPrefix to manifest', async () => {
    const phaManifest = {
      appWorker: {
        url: 'pha-worker.js',
      },
      routes: [
        'home',
        'about',
        'app/nest',
      ],
    };
    const manifest = await parseManifest(phaManifest, options);
    expect(manifest?.pages![0].path).toBe('https://url-prefix.com/home');
    expect(manifest?.pages![0].key).toBe('home');
    expect(manifest?.pages![0].title).toBe('title-home');
    expect(manifest?.pages![0].priority).toBe('low');
    expect(manifest?.pages![1].path).toBe('https://url-prefix.com/about?c=123');
    expect(manifest?.pages![2]?.frames![1].path).toBe('https://m.taobao.com');
    expect(manifest?.app_worker?.url).toBe('https://cdn-path.com/pha-worker.js');
  });

  it('multiple should work with frames', async () => {
    const phaManifest = {
      routes: [
        'home',
        'about',
        {
          defaultFrameIndex: 0,
          frames: ['home', 'about'],
        },
      ],
    };

    const manifest = await parseManifest(phaManifest, options);
    const remultipleManifests = await getMultipleManifest(manifest);

    expect(remultipleManifests['home']?.pages![0]?.frames?.length).toBe(2);
  });

  it('should work with enable pull refresh', async () => {
    const phaManifest = {
      appWorker: {
        url: 'pha-worker.js',
      },
      pullRefresh: {
        reload: false,
      },
      routes: [
        {
          path: '/',
          name: 'home',
        },
        {
          path: '/about',
          name: 'about',
        },
        {
          path: '/',
          name: 'frames',
          frames: [
            {
              name: 'frame1',
              url: 'https://m.taobao.com',
            },
            {
              name: 'frame2',
              url: 'https://m.taobao.com',
            },
          ],
        },
      ],
    };
    const manifest = await parseManifest(phaManifest, options);
    expect(manifest?.pages![0].enable_pull_refresh).toBe(true);
    expect(manifest?.pages![1].enable_pull_refresh).toBe(true);
    expect(manifest?.pages![2].enable_pull_refresh).toBe(true);
  });

  it('should work with enable pull refresh', async () => {
    const phaManifest = {
      appWorker: {
        url: 'pha-worker.js',
      },
      pullRefresh: {
        reload: true,
      },
      routes: [
        {
          path: '/',
          name: 'home',
          pullRefresh: {
            reload: true,
          },
        },
        {
          path: '/about',
          name: 'about',

        },
        {
          path: '/',
          name: 'frames',
          frames: [
            {
              name: 'frame1',
              url: 'https://m.taobao.com',
              pullRefresh: {
                reload: true,
              },
            },
            {
              name: 'frame2',
              url: 'https://m.taobao.com',
              pullRefresh: {
                reload: true,
              },
            },
          ],
        },
      ],
    };
    const manifest = await parseManifest(phaManifest, options);
    expect(manifest?.pages![0].pull_refresh).toBe(true);
    expect(manifest?.pages![1].pull_refresh).toBe(true);
    expect(manifest?.pages![2].frames![0].pull_refresh).toBe(true);
    expect(manifest?.pages![2].frames![1].pull_refresh).toBe(true);
  });

  it('pull refresh of manifest should be default value of page', async () => {
    const phaManifest = {
      appWorker: {
        url: 'pha-worker.js',
      },
      pullRefresh: {
        reload: true,
      },
      routes: [
        {
          path: '/',
          name: 'home',
        },
        {
          path: '/about',
          name: 'about',
        },
      ],
    };
    const manifest = await parseManifest(phaManifest, options);
    expect(manifest?.pages![0].pull_refresh).toBe(true);
    expect(manifest?.pages![1].pull_refresh).toBe(true);
  });

  it('enable pull refresh of manifest should be default value of page', async () => {
    const phaManifest = {
      appWorker: {
        url: 'pha-worker.js',
      },
      pullRefresh: true,
      routes: [
        {
          path: '/',
          name: 'home',
        },
        {
          path: '/about',
          name: 'about',
        },
      ],
    };
    const manifest = await parseManifest(phaManifest, options);
    expect(manifest?.pages![0].enable_pull_refresh).toBe(true);
    expect(manifest?.pages![1].enable_pull_refresh).toBe(true);
  });

  it('enable pull refresh of page should cover value of page', async () => {
    const phaManifest = {
      appWorker: {
        url: 'pha-worker.js',
      },
      pullRefresh: {
        reload: true,
      },
      routes: [
        {
          path: '/',
          name: 'home',
          pullRefresh: {
            reload: true,
          },
        },
        {
          path: '/about',
          name: 'about',
        },
      ],
    };
    const manifest = await parseManifest(phaManifest, options);
    expect(manifest?.pages![0].pull_refresh).toBe(true);
    expect(manifest?.pages![1].pull_refresh).toBe(true);
  });

  it('should work with enable pull refresh', async () => {
    const phaManifest = {
      appWorker: {
        url: 'pha-worker.js',
      },
      routes: [
        {
          path: '/',
          name: 'home',
          pullRefresh: true,
        },
      ],
    };
    const manifest = await parseManifest(phaManifest, options);
    expect(manifest?.pages![0].enable_pull_refresh).toBe(true);
  });

  it('should set document to manifest', async () => {
    const phaManifest = {
      routes: [
        'home',
        'about',
        'app/nest',
      ],
    };
    const manifest = await parseManifest(phaManifest, {
      ...options,
      template: true,
    });

    expect(manifest?.pages![0].document).toBe('<html><body>/home-document</body></html>');
    expect(manifest?.pages![1].document).toBe('<html><body>/about-document</body></html>');
    expect(manifest?.pages![2]?.frames![0].document).toBe('<html><body>/home-document</body></html>');
  });


  it('should not support template', async () => {
    const phaManifest = {
      routes: [
        'home',
        'about',
        'app/nest',
        '404',
      ],
    };
    const manifest = await parseManifest(phaManifest, {
      ...options,
      template: false,
    });

    expect(manifest.pages![0].script).toBeUndefined();
    expect(manifest.pages![0].stylesheet).toBeUndefined();
    expect(manifest.pages![0].document).toBeUndefined();
  });


  it('should config url to path when user config page.url', async () => {
    const phaManifest = {
      routes: [
        {
          path: '/',
          name: 'home',
          url: 'https://m.taobao.com',
        },
        {
          pageHeader: {
            url: 'https://m.taobao.com',
            source: 'pages/Header',
          },
          frames: [
            {
              path: '/frame1',
              name: 'frame1',
              url: 'https://m.taobao.com',
            },
          ],
        },
        'about',
      ],
      tabBar: {
        custom: true,
        items: ['home', 'frame1'],
        url: 'https://m.taobao.com',
      },
    };
    // @ts-ignore ignore type error with mix config key
    const manifest = await parseManifest(phaManifest, options);
    expect(manifest.pages![0].path).toBe('https://m.taobao.com');
    // @ts-ignore
    expect(manifest.pages![0].url).toBeUndefined();
    expect(manifest.pages![0].script).toBeUndefined();
    expect(manifest.pages![0].stylesheet).toBeUndefined();

    expect(manifest.pages![1]?.tab_header?.url).toBe('https://m.taobao.com');
    // @ts-ignore
    expect(manifest.pages![1]?.tab_header?.source).toBeUndefined();

    expect(manifest.pages![1]?.frames![0].path).toBe('https://m.taobao.com');
    // @ts-ignore
    expect(manifest.pages![1]?.frames![0].url).toBeUndefined();
    expect(manifest.pages![1]?.frames![0].script).toBeUndefined();
    expect(manifest.pages![1]?.frames![0].stylesheet).toBeUndefined();

    expect(manifest?.tab_bar?.url).toBe('https://m.taobao.com');
    // @ts-ignore
    expect(manifest?.tab_bar?.source).toBeUndefined();
    expect(manifest?.tab_bar?.items).toHaveLength(2);

    expect(manifest.pages![2].path).toBe('https://url-prefix.com/about?c=123');
  });

  it('should not cover url by pageUrl', async () => {
    const phaManifest = {
      routes: [
        {
          pageHeader: {
            source: 'pages/header',
          },
          frames: [
            {
              name: 'frame1',
              url: 'https://m.taobao.com',
            },
          ],
        },
      ],
      tabBar: {
        custom: true,
        source: 'pages/CustomTabBar',
        items: ['home', 'frame1'],
      },
    };

    const manifest = await parseManifest(phaManifest, {
      ...options,
      template: true,
    });

    expect(manifest.pages![0]?.tab_header?.url).toBe('https://url-prefix.com/header');
    expect(manifest.pages![0]?.tab_header?.html).toBe('<html><body>/header-document</body></html>');
    expect(manifest?.tab_bar?.url).toBe('https://url-prefix.com/CustomTabBar');
  });

  it('should not set html to tabBar when template is false', async () => {
    const phaManifest = {
      routes: [
        {
          pageHeader: {
            source: 'pages/header',
          },
          frames: [
            {
              name: 'frame1',
              url: 'https://m.taobao.com',
            },
          ],
        },
      ],
      tabBar: {
        custom: true,
        source: 'pages/CustomTabBar',
        items: ['home', 'frame1'],
      },
    };

    const manifest = await parseManifest(phaManifest, {
      ...options,
      template: false,
    });

    expect(manifest.pages![0]?.tab_header?.html).toBeUndefined();
  });

  it('error source without pages', async () => {
    const phaManifest = {
      tabBar: {
        source: 'components/CustomTabBar',
      },
    };
    try {
      await parseManifest(phaManifest, options);
      expect(true).toBe(false);
    } catch (err) {
      expect(true).toBe(true);
    }
  });

  it('url failed with new URL', async () => {
    const phaManifest = {
      tabBar: {
        source: 'pages/tabBar',
      },
    };
    const manifest = await parseManifest(phaManifest, {
      ...options,
      urlPrefix: '{{xxx}}/',
    });
    expect(manifest.tab_bar?.url).toBe('{{xxx}}/tabBar');
    expect(manifest.tab_bar?.name).toBe('{{xxx}}');
  });

  it('should not inject html when tabHeader & tabBar have url field', async () => {
    const phaManifest = {
      routes: [
        {
          pageHeader: {
            source: 'pages/Header',
            url: 'https://m.taobao.com',
          },
        },
      ],
      tabBar: {
        custom: true,
        source: 'pages/CustomTabBar',
        items: ['home', 'frame1'],
      },
    };

    const manifest = await parseManifest(phaManifest, {
      ...options,
      template: true,
    });
    expect(manifest.pages![0].tab_header?.url).toBe('https://m.taobao.com');
    expect(manifest.pages![0].tab_header?.html).toBeUndefined();

    expect(manifest.tab_bar?.url).toBe('https://url-prefix.com/CustomTabBar');
  });

  it('should work with static dataloader', async () => {
    const phaManifest = {
      title: 'test',
      routes: [
        {
          pageHeader: {},
          frames: [
            'blog',
            'home',
            'about',
          ],
        },
        'home',
        'about',
      ],
    };

    const staticDataloader = {
      key: 'dataLoader222',
      prefetch_type: 'mtop',
      api: 'query222',
      v: '0.0.1',
      data: {
        aaa: 111,
      },
      ext_headers: {},
    };

    const manifest = await parseManifest(phaManifest, {
      ...options,
      dataloaderConfig: {
        home: {
          loader: [
            () => {
              return new Promise((resolve) => {
                setTimeout(() => {
                  resolve({
                    name: 'About',
                  });
                }, 1 * 100);
              });
            },
            staticDataloader,
          ],
        },
      },
    });

    expect(manifest.pages![0].frames![1].data_prefetch?.length).toBe(1);
    expect(manifest.pages![1].data_prefetch?.length).toBe(2);
    expect(manifest.pages![2].data_prefetch).toBeUndefined();
  });

  it('prefetch of dataloader should not be decamelized', async () => {
    const phaManifest = {
      title: 'test',
      routes: [
        {
          pageHeader: {},
          frames: [
            'blog',
            'home',
            'about',
          ],
        },
        'home',
        'about',
      ],
    };

    const staticDataloader = {
      key: 'dataLoader222',
      prefetch_type: 'mtop',
      api: 'query222',
      v: '0.0.1',
      data: {
        aaa: 111,
        aaB: 222,
      },
      ext_headers: {},
    };

    const manifest = await parseManifest(phaManifest, {
      ...options,
      dataloaderConfig: {
        home: {
          loader: [
            () => {
              return new Promise((resolve) => {
                setTimeout(() => {
                  resolve({
                    name: 'About',
                  });
                }, 1 * 100);
              });
            },
            staticDataloader,
          ],
        },
      },
    });

    expect(manifest.pages![1].data_prefetch![1]?.data.aaB).toBe(222);
  });
});

describe('get multiple manifest', async () => {
  const options = {
    publicPath: 'https://cdn-path.com/',
    urlPrefix: 'https://url-prefix.com/',
    routesConfig: (await import(path.join(__dirname, './mockConfig.mjs')))?.default,
    excuteServerEntry: async () => mockServer,
    routeManifest: path.join(__dirname, './route-manifest.json'),
  };

  it('simple routes', async () => {
    const phaManifest = {
      routes: [
        'home',
        'about',
      ],
    };
    const manifest = await parseManifest(phaManifest, options);
    const multipleManifest = getMultipleManifest(manifest);
    expect(multipleManifest?.home?.pages?.length).toBe(1);
    expect(multipleManifest?.home?.data_prefetch).toMatchObject([{ api: 'test/api' }]);
    expect(multipleManifest?.about?.pages?.length).toBe(1);
  });

  it('routes with frame', async () => {
    const phaManifest = {
      routes: [
        {
          frames: [
            'app/nest',
            {
              url: 'https://m.taobao.com',
            },
          ],
        },
        'about',
      ],
    };
    const manifest = await parseManifest(phaManifest, options);
    const multipleManifest = getMultipleManifest(manifest);
    expect(multipleManifest?.['app/nest']?.pages?.length).toBe(1);
    expect(multipleManifest?.['app/nest']?.pages![0].frames?.length).toBe(2);
    expect(multipleManifest?.about?.pages?.length).toBe(1);
  });
});
