import fs from 'fs';
import path from 'path';

import { Locator, Page, TestInfo, expect } from '@playwright/test';

import theme_customization from '../../../../../backend/impress/configuration/theme/default.json';

export type BrowserName = 'chromium' | 'firefox' | 'webkit';
export const BROWSERS: BrowserName[] = ['chromium', 'webkit', 'firefox'];

export const CONFIG = {
  AI_BOT: {
    name: 'Docs AI',
    color: '#8bc6ff',
  },
  AI_FEATURE_ENABLED: true,
  AI_FEATURE_BLOCKNOTE_ENABLED: true,
  AI_FEATURE_LEGACY_ENABLED: true,
  API_USERS_SEARCH_QUERY_MIN_LENGTH: 3,
  CRISP_WEBSITE_ID: null,
  COLLABORATION_WS_URL: 'ws://localhost:4444/collaboration/ws/',
  COLLABORATION_WS_NOT_CONNECTED_READY_ONLY: true,
  CONVERSION_FILE_EXTENSIONS_ALLOWED: ['.docx', '.md'],
  CONVERSION_FILE_MAX_SIZE: 20971520,
  ENVIRONMENT: 'development',
  FRONTEND_CSS_URL: null,
  FRONTEND_JS_URL: null,
  FRONTEND_HOMEPAGE_FEATURE_ENABLED: true,
  FRONTEND_SILENT_LOGIN_ENABLED: false,
  FRONTEND_THEME: null,
  MEDIA_BASE_URL: 'http://localhost:8083',
  LANGUAGES: [
    ['en-us', 'English'],
    ['fr-fr', 'Français'],
    ['de-de', 'Deutsch'],
    ['nl-nl', 'Nederlands'],
    ['es-es', 'Español'],
  ],
  LANGUAGE_CODE: 'en-us',
  POSTHOG_KEY: {},
  SENTRY_DSN: null,
  TRASHBIN_CUTOFF_DAYS: 30,
  theme_customization,
} as const;

export const overrideConfig = async (
  page: Page,
  newConfig: { [_K in keyof typeof CONFIG]?: unknown },
) =>
  await page.route(/.*\/api\/v1.0\/config\/.*/, async (route) => {
    const request = route.request();
    if (request.method().includes('GET')) {
      await route.fulfill({
        json: {
          ...CONFIG,
          ...newConfig,
        },
      });
    } else {
      await route.continue();
    }
  });

export const keyCloakSignIn = async (
  page: Page,
  browserName: string,
  fromHome = true,
) => {
  if (fromHome) {
    await page.getByRole('button', { name: 'Start Writing' }).first().click();
  }

  const login = `user-e2e-${browserName}`;
  const password = `password-e2e-${browserName}`;

  await expect(
    page.locator('.login-pf #kc-header-wrapper').getByText('impress'),
  ).toBeVisible();

  if (await page.getByLabel('Restart login').isVisible()) {
    await page.getByLabel('Restart login').click();
  }

  await page.getByRole('textbox', { name: 'username' }).fill(login);
  await page.getByRole('textbox', { name: 'password' }).fill(password);
  await page.click('button[type="submit"]', { force: true });
};

export const getOtherBrowserName = (browserName: BrowserName) => {
  const otherBrowserName = BROWSERS.find((b) => b !== browserName);
  if (!otherBrowserName) {
    throw new Error('No alternative browser found');
  }
  return otherBrowserName;
};

export const randomName = (name: string, browserName: string, length: number) =>
  Array.from({ length }, (_el, index) => {
    return `${browserName}-${Math.floor(Math.random() * 10000)}-${index}-${name}`;
  });

export const openHeaderMenu = async (page: Page) => {
  const toggleButton = page.getByTestId('header-menu-toggle');
  await expect(toggleButton).toBeVisible();

  const isExpanded =
    (await toggleButton.getAttribute('aria-expanded')) === 'true';
  if (!isExpanded) {
    await toggleButton.click();
  }
};

export const closeHeaderMenu = async (page: Page) => {
  const toggleButton = page.getByTestId('header-menu-toggle');
  await expect(toggleButton).toBeVisible();

  const isExpanded =
    (await toggleButton.getAttribute('aria-expanded')) === 'true';
  if (isExpanded) {
    await toggleButton.click();
  }
};

export const toggleHeaderMenu = async (page: Page) => {
  const toggleButton = page.getByTestId('header-menu-toggle');
  await expect(toggleButton).toBeVisible();
  await toggleButton.click();
};

export const createDoc = async (
  page: Page,
  docName: string,
  browserName: string,
  length = 1,
  isMobile = false,
) => {
  const randomDocs = randomName(docName, browserName, length);

  for (let i = 0; i < randomDocs.length; i++) {
    if (isMobile) {
      await openHeaderMenu(page);
    }

    await page
      .getByRole('button', {
        name: 'New doc',
      })
      .click();

    await page.waitForURL('**/docs/**', {
      timeout: 10000,
      waitUntil: 'networkidle',
    });

    const input = page.getByLabel('Document title');
    await expect(input).toBeVisible();
    await expect(input).toHaveText('');

    await input.fill(randomDocs[i]);
    await input.blur();
  }

  return randomDocs;
};

export const verifyDocName = async (page: Page, docName: string) => {
  await expect(
    page.getByLabel('It is the card information about the document.'),
  ).toBeVisible({
    timeout: 10000,
  });

  /*replace toHaveText with toContainText to handle cases where emojis or other characters might be added*/
  try {
    await expect(
      page.getByRole('textbox', { name: 'Document title' }),
    ).toContainText(docName, {
      timeout: 3000,
    });
  } catch {
    await expect(page.getByRole('heading', { name: docName })).toBeVisible();
  }
};

export const getGridRow = async (page: Page, title: string) => {
  const docsGrid = page.getByRole('grid');
  await expect(docsGrid).toBeVisible();
  await expect(page.getByTestId('grid-loader')).toBeHidden();

  const rows = docsGrid.getByRole('row');

  const row = rows
    .filter({
      hasText: title,
    })
    .first();

  await expect(row).toBeVisible();

  return row;
};

interface GoToGridDocOptions {
  nthRow?: number;
  title?: string;
}
export const goToGridDoc = async (
  page: Page,
  { nthRow = 1, title }: GoToGridDocOptions = {},
) => {
  const header = page.locator('header').first();
  await header.locator('h1').getByText('Docs').click();

  const docsGrid = page.getByTestId('docs-grid');
  await expect(docsGrid).toBeVisible();
  await expect(page.getByTestId('grid-loader')).toBeHidden();

  const rows = docsGrid.getByRole('row');

  const row = title
    ? rows.filter({
        hasText: title,
      })
    : rows.nth(nthRow);

  await expect(row).toBeVisible();

  const docTitleContent = row.getByTestId('doc-title').first();
  const docTitle = await docTitleContent.textContent();
  expect(docTitle).toBeDefined();

  await row.getByRole('link').first().click();

  return docTitle as string;
};

export const updateDocTitle = async (page: Page, title: string) => {
  const input = page.getByRole('textbox', { name: 'Document title' });
  await expect(input).toHaveText('');
  await expect(input).toBeVisible();
  await input.click();
  await input.fill(title, {
    force: true,
  });
  await input.click();
  await input.blur();
  await verifyDocName(page, title);
};

export const waitForResponseCreateDoc = (page: Page) => {
  return page.waitForResponse(
    (response) =>
      response.url().includes('/documents/') &&
      response.url().includes('/children/') &&
      response.request().method() === 'POST',
  );
};

export const mockedDocument = async (page: Page, data: object) => {
  await page.route(/\**\/documents\/\**/, async (route) => {
    const request = route.request();
    if (
      request.method().includes('GET') &&
      !request.url().includes('page=') &&
      !request.url().includes('versions') &&
      !request.url().includes('accesses') &&
      !request.url().includes('invitations')
    ) {
      const { abilities, ...doc } = data as unknown as {
        abilities?: Record<string, unknown>;
      };
      await route.fulfill({
        json: {
          id: 'mocked-document-id',
          content: '',
          title: 'Mocked document',
          path: '000000',
          abilities: {
            destroy: false, // Means not owner
            link_configuration: false,
            versions_destroy: false,
            versions_list: true,
            versions_retrieve: true,
            accesses_manage: false, // Means not admin
            update: false,
            partial_update: false, // Means not editor
            retrieve: true,
            link_select_options: {
              public: ['reader', 'editor'],
              authenticated: ['reader', 'editor'],
              restricted: null,
            },
            ...abilities,
          },
          link_reach: 'restricted',
          computed_link_reach: 'restricted',
          computed_link_role: 'reader',
          ancestors_link_reach: null,
          ancestors_link_role: null,
          created_at: '2021-09-01T09:00:00Z',
          user_role: 'owner',
          ...doc,
        },
      });
    } else {
      await route.continue();
    }
  });
};

export const mockedListDocs = async (page: Page, data: object[] = []) => {
  await page.route(/\**\/documents\/\**/, async (route) => {
    const request = route.request();
    if (request.method().includes('GET') && request.url().includes('page=')) {
      await route.fulfill({
        json: {
          count: data.length,
          next: null,
          previous: null,
          results: data,
        },
      });
    }
  });
};

export const expectLoginPage = async (page: Page) =>
  await expect(
    page.getByRole('heading', { name: 'Collaborative writing' }),
  ).toBeVisible({
    timeout: 10000,
  });

// language helper
export const TestLanguage = {
  English: {
    label: 'English',
    expectedLocale: ['en-us'],
  },
  French: {
    label: 'Français',
    expectedLocale: ['fr-fr'],
  },
  German: {
    label: 'Deutsch',
    expectedLocale: ['de-de'],
  },
  Swedish: {
    label: 'Svenska',
    expectedLocale: ['sv-se'],
  },
} as const;

type TestLanguageKey = keyof typeof TestLanguage;
type TestLanguageValue = (typeof TestLanguage)[TestLanguageKey];

export async function waitForLanguageSwitch(
  page: Page,
  lang: TestLanguageValue,
) {
  await page.route(/\**\/api\/v1.0\/users\/\**/, async (route, request) => {
    if (request.method().includes('PATCH')) {
      await route.fulfill({
        json: {
          language: lang.expectedLocale[0],
        },
      });
    } else {
      await route.continue();
    }
  });

  const header = page.locator('header').first();
  const languagePicker = header.locator('.--docs--language-picker-text');
  const isAlreadyTargetLanguage = await languagePicker
    .innerText()
    .then((text) => text.toLowerCase().includes(lang.label.toLowerCase()));

  if (isAlreadyTargetLanguage) {
    return;
  }

  await languagePicker.click();

  await page.getByRole('menuitem', { name: lang.label }).click();
}

export const clickInEditorMenu = async (page: Page, textButton: string) => {
  await page.getByRole('button', { name: 'Open the document options' }).click();
  await page.getByRole('menuitem', { name: textButton }).click();
};

export const clickInGridMenu = async (
  page: Page,
  row: Locator,
  textButton: string,
) => {
  await row
    .getByRole('button', { name: /Open the menu of actions for the document/ })
    .click();
  await page.getByRole('menuitem', { name: textButton }).click();
};

export const writeReport = async (
  testInfo: TestInfo,
  filename: string,
  attachName: string,
  buffer: Buffer,
  contentType: string,
) => {
  const REPORT_DIRNAME = 'extra-report';
  const REPORT_NAME = 'test-results';
  const outDir = testInfo
    ? path.join(testInfo.outputDir, REPORT_DIRNAME, path.parse(filename).name)
    : path.join(
        process.cwd(),
        REPORT_NAME,
        REPORT_DIRNAME,
        path.parse(filename).name,
      );

  fs.mkdirSync(outDir, { recursive: true });
  const pathToFile = path.join(outDir, filename);
  fs.writeFileSync(pathToFile, buffer);
  await testInfo.attach(attachName, {
    path: pathToFile,
    contentType: contentType,
  });
};
