/// <reference path="../test-types.ts"/>

import * as _ from 'lodash';
import assert = require('../utils/ty-assert');
import server = require('../utils/server');
import utils = require('../utils/utils');
import { buildSite } from '../utils/site-builder';
import { TyE2eTestBrowser } from '../utils/pages-for';
import settings = require('../utils/settings');
import lad = require('../utils/log-and-die');
import c = require('../test-constants');





let richBrowserA;
let richBrowserB;
let owen: Member;
let owensBrowser: TyE2eTestBrowser;
let maja: Member;
let majasBrowser: TyE2eTestBrowser;
let strangersBrowser: TyE2eTestBrowser;

let siteIdAddress: IdAddress;
let siteId;

let forum: TwoPagesTestForum;

const apiSecret: TestApiSecret = {
  nr: 1,
  userId: c.SysbotUserId,
  createdAt: c.MinUnixMillis,
  deletedAt: undefined,
  isDeleted: false,
  secretKey: 'publicE2eTestSecretKeyAbc123',
};


const evil_example_com = 'evil.example.com';
const script_gets_removed = 'script_gets_removed';
const onclick = 'onclick';


const categoryExtId = 'cat_ext_id';
const UpsPageOneBodyStart = 'UpsPageOneBodyStart';

const pageOneToUpsert_commonMarkBody = {
  // id: assigned by the server
  extId: 'ups_page_one_ext_id',
  pageType: c.TestPageRole.Idea,
  categoryRef: 'extid:' + categoryExtId,
  authorRef: 'username:owen_owner',
  title: 'UpsPageOneTitle',
  // slug: generated by the server
  body: `
### ${UpsPageOneBodyStart}

\`\`\`
testyFunction("par_ame_te_r");
var variable = 123;
const arrowFn = () => "five & 'three'";

    // indented 4 spaces
    char_p value = "Rikki-Tikki-Tavi";
    int & myHero = reinterpret_cast<int &>(value);
    cout << myHero << endl;

    # bash
    cat mice.bat 2>&1 | tee -a pets-left.log

\`\`\`

**bold_font**

_italics_

> Quoty-Quote

<a href="#" onclick="alert('${script_gets_removed}')">link_text_01</a>
<a href="javascript:alert('${script_gets_removed}');">link_text_02</a>
<script src="http://${evil_example_com}/so_evil_script.js"></script>
<script src="http://${evil_example_com}/so_evil_script.js">${script_gets_removed}</script>
`,
};

const pageOneExpectedSlug = '/-2/upspageonetitle';

const pageTwoToUpsert_htmlBody = {
  extId: 'UpsPageTwoExtId',
  pageType: c.TestPageRole.Question,
  categoryRef: 'extid:' + categoryExtId,
  authorRef: 'username:owen_owner',
  title: 'UpsPageTwoTitle',
  //slug: 'page-two-custom-slug', — custom slugs not yet supported via the upsert api
  body: `UpsPageTwoBody
    <h1>h1_stays</h1>
    <h4>h4_stays</h4>
    <b>bold_stays</b>
    <p>para_stays</p>
    <pre>pre_stays</pre>
    <code>code_stays</code>
    <a rel="nofollow" href="http://link_url_stays.example.com">link_text_stays</a>
    <img alt="img_alt_stays" src="https://nice.example.com/img.jpg">
    <blockquote>blockquote_stays</blockquote>
    <ul><li>list_stays</li></ul>
    <table><tbody><td>table_stays</td></tbody></table>
    <a href="#" onclick="alert('${script_gets_removed}')">link_text_03</a>
    <a href="javascript:alert('${script_gets_removed}');">link_text_04</a>
    <script src="http://${evil_example_com}/so_evil_script.js"></script>
    <script src="http://${evil_example_com}/so_evil_script.js">${script_gets_removed}</script>
    **not_bold** \`not_code\`
    last_line_stays`,
  bodyMarkupLang: 'HTML',
};

const pageTwoEditedToUpsert = {
  ...pageTwoToUpsert_htmlBody,
  title: 'Page Two Edited Title',
  body: 'Page two edited body.',
  slug: 'page-two-edited-slug',
  pageType: c.TestPageRole.Question,
};

const pageThreeToUpsert_longTitle = {
  extId: 'UpsPageThreeExtId',
  pageType: c.TestPageRole.Problem,
  categoryRef: 'extid:' + categoryExtId,
  authorRef: 'username:owen_owner',
  // Talkyard should generate a max 100 chars slug, and remove non-ASCII (for now),
  // and leading and trailing '-' and replace blanks and punctuation with '-'.
  // [TyT692SKBDWJ74]
  title: '---UpsPageThreeTtl - Ååå 中文 oh so many! chars?: 123456789 123456789 123456789 123456789 123456789 1234567-----.*!?#$+",.%^&--89 1-----789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789',
  body: 'UpsPageThreeBody',
};

const pageThreeExpectedTitle =
    pageThreeToUpsert_longTitle.title.substr(0, c.MaxTitleLength);

const pageThreeExpectedSlug =
    'upspagethreettl-aaa-oh-so-many-chars-123456789-123456789-123456789-123456789-123456789-1234567-89-1';

describe("api-upsert-pages   TyT603PKRAEPGJ5", () => {

  if (settings.prod) {
    console.log("Skipping this spec — the server needs to have upsert conf vals enabled."); // E2EBUG
    return;
  }


  // ----- Create site, with API enabled

  it("import a site", () => {
    const builder = buildSite();
    forum = builder.addTwoPagesForum({
      title: "Ups Pages E2E Test",
      members: ['owen', 'maja', 'maria', 'michael'],
    });
    assert.ok(builder.getSite() === forum.siteData);
    const site: SiteData2 = forum.siteData;
    site.settings.enableApi = true;
    site.apiSecrets = [apiSecret];
    siteIdAddress = server.importSiteData(forum.siteData);
    siteId = siteIdAddress.id;
  });

  it("initialize people", () => {
    richBrowserA = new TyE2eTestBrowser(browserA);
    richBrowserB = new TyE2eTestBrowser(browserB);

    owen = forum.members.owen;
    owensBrowser = richBrowserA;

    maja = forum.members.maja;
    majasBrowser = richBrowserB;
    strangersBrowser = richBrowserB;
  });


  // ----- Assign ext id to a category   (dupl code [05KUDTEDW24])

  it("Owen goes to Category A, logs in", () => {
    owensBrowser.forumTopicList.goHere({
        origin: siteIdAddress.origin,
        categorySlug: forum.categories.categoryA.slug });
    owensBrowser.topbar.clickLogin();
    owensBrowser.loginDialog.loginWithPassword(owen);
  });

  it("Opens the category", () => {
    owensBrowser.forumButtons.clickEditCategory();
  });

  it("... edits it: assigns an External ID", () => {
    owensBrowser.categoryDialog.fillInFields({ extId: categoryExtId });
  });

  it("... saves", () => {
    owensBrowser.categoryDialog.submit();
  });

  // ----- Upsert page via API

  let upsertResponse;
  let firstUpsertedPage: any;

  it("Upsert a page", () => {
    upsertResponse = server.apiV0.upsertSimple({
      origin: siteIdAddress.origin,
      apiRequesterId: c.SysbotUserId,
      apiSecret: apiSecret.secretKey,
      data: {
        pages: [pageOneToUpsert_commonMarkBody],
      },
    });
  });

  it("... gets back the upserted page in the server's response", () => {
    console.log("Page ups resp:\n\n:" + JSON.stringify(upsertResponse, undefined, 2));

    assert.eq(upsertResponse.pages.length, 1);
    firstUpsertedPage = upsertResponse.pages[0];

    assert.eq(firstUpsertedPage.urlPaths.canonical, pageOneExpectedSlug);
    assert.eq(expectedUrlPath(firstUpsertedPage, pageOneToUpsert_commonMarkBody), pageOneExpectedSlug);

    assert.eq(firstUpsertedPage.id, '2');
    assert.eq(firstUpsertedPage.pageType, c.TestPageRole.Idea);
    utils.checkNewPageFields(firstUpsertedPage, {
      categoryId: forum.categories.categoryA.id,
      authorId: owen.id,
    });
  });


  it("The upserted page is not yet visible in the topic list", () => {
    owensBrowser.forumTopicList.assertNumVisible(2, { wait: true });
  });

  it("... but Owen refreshes the page", () => {
    owensBrowser.refresh();
  });

  it("... now the upserted page is in the page list", () => {
    owensBrowser.forumTopicList.assertNumVisible(3, { wait: true });
  });

  it("... all topics have the epected titles", () => {
    owensBrowser.forumTopicList.assertTopicTitlesAreAndOrder([
        pageOneToUpsert_commonMarkBody.title,
        forum.topics.byMichaelCategoryA.title,
        forum.topics.byMariaCategoryA.title]);
  });


  it("Owen opens the upserted topic", () => {
    owensBrowser.forumTopicList.navToTopic(pageOneToUpsert_commonMarkBody.title);
  });

  it("... it has the correct title", () => {
    owensBrowser.topic.waitForPostAssertTextMatches(c.TitleNr, pageOneToUpsert_commonMarkBody.title);
  });

  let bodyText: string;

  it("... and body", () => {
    owensBrowser.topic.waitForPostAssertTextMatches(c.BodyNr, UpsPageOneBodyStart);
    bodyText = owensBrowser.topic.getPostHtml(c.BodyNr);

    // The ComomonMark source should now be HTML.
    // These tests will do for now?:

    // This: ### ... got converted to <h3> ...
    assert.excludes(bodyText, '###');
    assert.includes(bodyText, '<h3>UpsPageOneBodyStart</h3>');
    // '# bash' is still there though — that '#' is in a code block.
    assert.includes(bodyText, '    # bash');

    // Step by step, simpler to troubleshoot:

    // The code block backticks got converted to <pre><code>:
    assert.excludes(bodyText, '`');
    assert.includes(bodyText, '<pre>');
    assert.includes(bodyText, '<pre><code>testyFunction');
    assert.includes(bodyText, '<pre><code>testyFunction("par_ame_te_r");');
    assert.includes(bodyText, 'var variable = 123;');

    // In a code block, '> < &' get escaped.
    assert.includes(bodyText, 'arrowFn = () =&gt;');
    assert.includes(bodyText, 'cout &lt;&lt; myHero &lt;&lt; endl;');
    assert.includes(bodyText, 'cat mice.bat 2&gt;&amp;1 | tee');

    assert.includes(bodyText, '</code></pre>');

    // The bold font stars '**' got converted to <strong>.
    assert.excludes(bodyText, '*');
    assert.includes(bodyText, '<p><strong>bold_font</strong></p>');

    assert.includes(bodyText, '<p><em>italics</em></p>');
    assert.includes(bodyText, '<blockquote>');
    assert.includes(bodyText, '<p>Quoty-Quote</p>');
    assert.includes(bodyText, '</blockquote>');
    // The link text was kept (but script links and onclick removed, tested below).
    assert.includes(bodyText, 'link_text_01');
    assert.includes(bodyText, 'link_text_02');

    // And the quote line (bbefore getting transformed to <tag>...</tag>).
    assert.excludes(bodyText, '> quote');
  });

  it("... script tags gone  TyT306KTG24M", () => {
    // First, test the tests.
    assert.eq('script', c.ScriptTagName);
    assert.includes(pageOneToUpsert_commonMarkBody.body, UpsPageOneBodyStart);
    assert.includes(pageOneToUpsert_commonMarkBody.body, c.javascript);
    assert.includes(pageOneToUpsert_commonMarkBody.body, onclick);
    assert.includes(pageOneToUpsert_commonMarkBody.body, c.ScriptTagName);
    assert.includes(pageOneToUpsert_commonMarkBody.body, script_gets_removed);
    assert.includes(pageOneToUpsert_commonMarkBody.body, evil_example_com);
    // Now the real test:
    assert.excludes(bodyText, c.javascript);
    assert.excludes(bodyText, onclick);
    assert.excludes(bodyText, c.ScriptTagName);
    assert.excludes(bodyText, script_gets_removed);
    assert.excludes(bodyText, evil_example_com);
  });

  it("... and url", () => {
    assert.eq(owensBrowser.urlPath(), pageOneExpectedSlug);
  });


  it("Owen goes to the page URL path from the topic response", () => {
    owensBrowser.go2('/');
    owensBrowser.go2(siteIdAddress.origin + firstUpsertedPage.urlPaths.canonical);
  });

  it("... and, again, sees the correct page title and body", () => {
    owensBrowser.topic.waitForPostAssertTextMatches(
          c.TitleNr, pageOneToUpsert_commonMarkBody.title);
    owensBrowser.topic.waitForPostAssertTextMatches(
          c.BodyNr, UpsPageOneBodyStart);
  });



  // ----- Upserting many pages

  it("Owen upserts two pages, in the same API request", () => {
    upsertResponse = server.apiV0.upsertSimple({
        origin: siteIdAddress.origin,
        apiRequesterId: c.SysbotUserId,
        apiSecret: apiSecret.secretKey,
        data: {
          pages: [pageTwoToUpsert_htmlBody, pageThreeToUpsert_longTitle],
        }});
  });

  it("... gets back two upserted pages in the response", () => {
    console.log("2 pages ups resp:\n\n:" +
        JSON.stringify(upsertResponse.pages.length, undefined, 2));
    assert.eq(upsertResponse.pages.length, 2);
  });

  function expectedUrlPath(page, toUps: { title?: string, slug?: string }): string {
    // This isn't exactly how the server does it, but works for this e2e test.
    const slug = toUps.slug || toUps.title.toLowerCase().replace(/ /g, '-').substr(0, 100);
    return `/-${page.id}/${slug}`;
  }

  it("... the first one looks correct", () => {
    const upsPage = upsertResponse.pages[0];
    assert.eq(upsPage.id, '3');
    assert.eq(upsPage.urlPaths.canonical, expectedUrlPath(upsPage, pageTwoToUpsert_htmlBody));
    assert.eq(upsPage.pageType, c.TestPageRole.Question);
    utils.checkNewPageFields(upsPage, {
      categoryId: forum.categories.categoryA.id,
      authorId: owen.id,
    });
  });

  it("... the 2nd, likewise", () => {
    const upsPage = upsertResponse.pages[1];
    assert.eq(upsPage.urlPaths.canonical, expectedUrlPath(upsPage, { slug: pageThreeExpectedSlug }));
    assert.eq(upsPage.pageType, c.TestPageRole.Problem);
    utils.checkNewPageFields(upsPage, {
      categoryId: forum.categories.categoryA.id,
      authorId: owen.id,
    });
  });

  it("Now there're 2 more pages", () => {
    owensBrowser.topbar.clickAncestor(forum.categories.categoryA.name);
    owensBrowser.forumTopicList.assertNumVisible(5, { wait: true });
  });

  it("... all topics have the epected titles", () => {
    owensBrowser.forumTopicList.assertTopicTitlesAreAndOrder([
        // Now topic no 3 is first, but below: TyT602FKUDLSV
        pageThreeExpectedTitle,
        pageTwoToUpsert_htmlBody.title,
        pageOneToUpsert_commonMarkBody.title,
        forum.topics.byMichaelCategoryA.title,
        forum.topics.byMariaCategoryA.title]);
  });

  it("No notf emails sent — by default, no notfs for upserts  TyT305WKTUC2", () => {
    const { num, addrsByTimeAsc } = server.getEmailsSentToAddrs(siteId);
    assert.eq(num, 0, `Emails sent to: ${addrsByTimeAsc}`);
  });



  // ----- The HTML got sanitized but not too much

  it("Owen opens the upserted & upsert-edited page two", () => {
    owensBrowser.forumTopicList.navToTopic(pageTwoToUpsert_htmlBody.title
        //  later: pageTwoEditedToUpsert.title);
        );
  });

  it("... the upserted HTML tags are there, '```' and '**' unchanged", () => {
    owensBrowser.topic.waitUntilPostHtmlMatches(c.BodyNr, [
        '<h1>h1_stays</h1>',
        '<h4>h4_stays</h4>',
        '<b>bold_stays</b>',
        '<p>para_stays</p>',
        '<pre>pre_stays</pre>',
        '<code>code_stays</code>',
        '<pre>pre_stays</pre>',
        'link_url_stays',
        'nofollow',
        '<a href="http://link_url_stays.example.com" rel="nofollow noopener">link_text_stays</a>',
        'img_alt_stays',
        '<img alt="img_alt_stays" src="https://nice.example.com/img.jpg">',
        /<blockquote>[\s]*blockquote_stays[\s]*<\/blockquote>/,
        /<ul>[\s]*<li>[\s]*list_stays[\s]*<\/li>[\s]*<\/ul>/,
        /<table>[\s]*<tbody>[\s]*<tr>[\s]*<td>[\s]*table_stays[\s]*<\/td>[\s]*<\/tr>[\s]*<\/tbody>[\s]*<\/table>/,
        /link_text_03/,
        /link_text_04/,
        /\*\*not_bold\*\* `not_code`/,
        'last_line_stays']);
  });

  it("... but not the <script> tag; it got removed by the sanitizer  TyT306KTG24M", () => {
    // First, test the tests.
    assert.includes(pageTwoToUpsert_htmlBody.body, 'h1_stays');
    assert.includes(pageTwoToUpsert_htmlBody.body, c.javascript);
    assert.includes(pageTwoToUpsert_htmlBody.body, onclick);
    assert.includes(pageTwoToUpsert_htmlBody.body, c.ScriptTagName);
    assert.includes(pageTwoToUpsert_htmlBody.body, script_gets_removed);
    assert.includes(pageTwoToUpsert_htmlBody.body, evil_example_com);
    assert.includes(pageTwoToUpsert_htmlBody.body, 'so_evil_script');
    // Now the real test:
    owensBrowser.topic.assertPostHtmlDoesNotMatch(c.BodyNr, [
        c.javascript,
        onclick,
        script_gets_removed,
        c.ScriptTagName,
        '<script',
        '</script>',
        evil_example_com,
        'so_evil_script']);
  });



  // ----- Edit page, via upsert API  — no, this requires ActionPatch [ACTNPATCH]

  /* Not yet implemented  TyT650KWUDEPJ03g
  it("Owen edit-upserts the 2nd page: a new name, slug, etc", () => {
    upsertResponse = server.apiV0.upsertSimple({
        origin: siteIdAddress.origin,
        apiRequesterId: c.SysbotUserId,
        apiSecret: apiSecret.secretKey,
        data: {
          pages: [pageTwoEditedToUpsert],
        }});
  });

  let upsEditedPageTwo;

  it("... the server replies; the page has now the new title, slug, etc", () => {
    assert.eq(upsertResponse.pages.length, 1);
    upsEditedPageTwo = upsertResponse.pages[0];
  });

  it("Owen goest to the urlPaths.activeTopics category URL path, for the now edited slug", () => {
    owensBrowser.go(siteIdAddress.origin + upsEditedPageTwo.urlPaths.canonical);
  });

  it("... and sees the new title and body", () => {
    owensBrowser.topic.waitForPostAssertTextMatches(c.TitleNr, pageTwoEditedToUpsert.title);
    owensBrowser.topic.waitForPostAssertTextMatches(c.BodyNr, pageTwoEditedToUpsert.body);
  });


  it("The previous page slug redirects to the new  [TyT503KRDH24]", () => {
    // TESTS_MISSING not yet impl
  });


  it("... Owen returns to the topic list page", () => {
    owensBrowser.forumTopicList.goHere({
        categorySlug: forum.categories.categoryA.slug });
  });

  it("... the page title did change", () => {
    owensBrowser.forumTopicList.assertNumVisible(5, { wait: true });
    owensBrowser.forumTopicList.assertTopicTitlesAreAndOrder([
        pageThreeToUpsert.title,
        pageTwoEditedToUpsert.title,   // <—— new title
        pageOneToUpsert.title,
        forum.topics.byMichaelCategoryA.title,
        forum.topics.byMariaCategoryA.title]);
  });
  */


  // TESTS_MISSING: verify page body updated —   [YESUPSERT]
  // actually, not yet impl. Pages are currently only *in*serted.
  // (So right now, one cannot upsert a new page body or title.)



  // ----- Actually use upserted page, via UI

  const PageTwoTitleManuallyEdited = 'PageTwoTitleManuallyEdited';
  const PageTwoBodyManuallyEdited = 'PageTwoBodyManuallyEdited';
  const PageTwoReplyMentionsMaja = 'PageTwoReply @maja';

  it("... edits the title", () => {
    owensBrowser.complex.editPageTitle(PageTwoTitleManuallyEdited);
  });

  it("... and text", () => {
    owensBrowser.complex.editPageBody(PageTwoBodyManuallyEdited, { append: true });
  });

  it("... and posts a reply, mentions Maja", () => {
    owensBrowser.complex.replyToOrigPost(PageTwoReplyMentionsMaja);
  });

  it("Owen reloads the page", () => {
    owensBrowser.topic.refreshUntilPostNrAppears(c.FirstReplyNr);
  });

  it("... all fine, after reload", () => {
    owensBrowser.topic.waitForPostAssertTextMatches(c.TitleNr, PageTwoTitleManuallyEdited);
    owensBrowser.topic.waitForPostAssertTextMatches(c.BodyNr, PageTwoBodyManuallyEdited);
    owensBrowser.topic.waitForPostAssertTextMatches(c.FirstReplyNr, PageTwoReplyMentionsMaja);
  });

  it("Maja gets a notf email", () => {
    server.waitUntilLastEmailMatches(
        siteIdAddress.id, maja.emailAddress,
        [PageTwoReplyMentionsMaja], majasBrowser);
  });

  it("... but no one else, no notfs for upserts  TyT305WKTUC2", () => {
    const { num, addrsByTimeAsc } = server.getEmailsSentToAddrs(siteId);
    const oneEmailToMaja = 1;
    assert.eq(num, oneEmailToMaja, `Emails sent to: ${addrsByTimeAsc}`);
  });

  it("Owen returns to the category", () => {
    owensBrowser.topbar.clickAncestor(forum.categories.categoryA.name);  // TyT602FKUDLSV
  });

  it("... the topic list got updated, with the new title", () => {
    owensBrowser.forumTopicList.assertNumVisible(5, { wait: true });
    owensBrowser.forumTopicList.assertTopicTitlesAreAndOrder([
        PageTwoTitleManuallyEdited, // topic 2 first — got bumped  TyT602FKUDLSV
        pageThreeExpectedTitle,     // ... above topic 3
        pageOneToUpsert_commonMarkBody.title,
        forum.topics.byMichaelCategoryA.title,
        forum.topics.byMariaCategoryA.title]);
  });

});

