import { expect } from '@playwright/test';
import { test } from '../../fixtures/group.fixture';
import { EditPage } from '../../pages/edit.page';
import { ReviewPage } from '../../pages/review.page';
import { SearchPage } from '../../pages/search.page';
import { SingleSequenceSubmissionPage } from '../../pages/submission.page';
import { SequenceDetailPage } from '../../pages/sequence-detail.page';
import { randomUUID } from 'crypto';

const TEST_SEQUENCE =
    'ATTGATCTCATCATTTACCAATTGGAGACCGTTTAACTAGTCAATCCCCCATTTGGGGGCATTCCTAAAGTGTTGCAA' +
    'AGGTATGTGGGTCGTATTGCTTTGCCTTTTCCTAACCTGGCTCCTCCTACAATTCTAACCTGCTTGATAAGTGTGATTACCTG' +
    'AGTAATAGACTAATTTCGTCCTGGTAATTAGCATTTTCTAGTAAAACCAATACTATCTCAAGTCCTAAGAGGAGGTGAGAAGA' +
    'GGGTCTCGAGGTATCCCTCCAGTCCACAAAATCTAGCTAATTTTAGCTGAGTGGACTGATTACTCTCATCACACGCTAACTAC' +
    'TAAGGGTTTACCTGAGAGCCTACAACATGGATAAACGGGTGAGAGGTTCATGGGCCCTGGGAGGACAATCTGAAGTTGATCTT' +
    'GACTACCACAAAATATTAACAGCCGGGCTTTCGGTCCAACAAGGGATTGTGCGACAAAGAGTCATCCCGGTATATGTTGTGAG' +
    'TGATCTTGAGGGTATTTGTCAACATATCATTCAGGCCTTTGAAGCAGGCGTAGATTTCCAAGATAATGCTGACAGCTTCCTTT' +
    'TACTTTTATGTTTACATCATGCTTACCAAGGAGATCATAGGCTCTTCCTCAAAAGTGATGCAGTTCAATACTTAGAGGGCCAT' +
    'GGTTTCAGGTTTGAGGTCCGAGAAAAGGAGAATGTGCACCGTCTGGATGAATTGTTGCCCAATGTCACCGGTGGAAAAAATCT' +
    'TAGGAGAACATTGGCTGCAATGCCTGAAGAGGAGACAACAGAAGCTAACGCTGGTCAGTTTTTATCCTTTGCCAGTTTGTTTC' +
    'TACCCAAACTTGTCGTTGGGGAGAAAGCGTGTCTGGAAAAAGTACAAAGGCAGATTCAGGTCCATGCAGAACAAGGGCTCATT' +
    'CAATATCCAACTTCCTGGCAATCAGTTGGACACATGATGGTGATCTTCCGTTTGATGAGAACAAACTTTTTAATCAAGTTCCT' +
    'ACTAATACATCAGGGGATGCACATGG';

// TEST_SEQUENCE with one point mutation (SNP) and one in-frame (3-nt) deletion, used to
// give a third revision a slightly different sequence so the diff shows sequence-derived
// changes (length, mutations) between versions. The 3-nt deletion avoids a frameshift so
// the revision still releases cleanly.
const SNP_INDEX = 60;
const DELETION_START = 120;
const THIRD_REVISION_SEQUENCE = (() => {
    const swapped = TEST_SEQUENCE[SNP_INDEX] === 'A' ? 'G' : 'A';
    const withSnp =
        TEST_SEQUENCE.slice(0, SNP_INDEX) + swapped + TEST_SEQUENCE.slice(SNP_INDEX + 1);
    return withSnp.slice(0, DELETION_START) + withSnp.slice(DELETION_START + 3);
})();

test.describe('Sequence version banners', () => {
    test('shows correct banners for revoked, revocation, deprecated, and revised entries', async ({
        page,
        groupId,
    }) => {
        test.setTimeout(300_000);
        void groupId;

        const uuid = randomUUID();
        const submissionPage = new SingleSequenceSubmissionPage(page);
        const search = new SearchPage(page);

        // Submit two sequences - one to revise, one to revoke
        await page.goto('/');
        await submissionPage.completeSubmission(
            {
                submissionId: 'to-revise',
                collectionCountry: 'France',
                collectionDate: '2023-01-01',
                authorAffiliations: uuid,
            },
            { main: TEST_SEQUENCE },
        );

        await page.goto('/');
        const reviewPage = await submissionPage.completeSubmission(
            {
                submissionId: 'to-revoke',
                collectionCountry: 'Germany',
                collectionDate: '2023-01-02',
                authorAffiliations: uuid,
            },
            { main: TEST_SEQUENCE },
        );

        // Release both sequences
        await reviewPage.waitForZeroProcessing();
        await reviewPage.releaseAndGoToReleasedSequences();

        // Wait for sequences to appear in search
        while (!(await page.getByText('Search returned 2 sequences').isVisible())) {
            await page.reload();
            await page.waitForTimeout(2000);
        }

        // Find and revise the France sequence
        await search.ebolaSudan();
        await search.enableSearchFields('Author affiliations');
        await search.fill('Author affiliations', uuid);
        await search.select('Collection country', 'France');

        // Get the accession of the sequence to revise
        const toReviseLink = page.getByRole('link', { name: /LOC_[A-Z0-9]+\.1/ });
        const toReviseId = await toReviseLink.textContent();
        const toReviseAccession = toReviseId.split('.')[0];

        // Click on the sequence and revise it
        await page.getByRole('cell', { name: 'France' }).click();
        await search.reviseSequence();
        await page.getByLabel('Collection date').fill('2023-06-15');
        await page.getByRole('button', { name: /proceed to Approval/ }).click();
        await page.getByRole('button', { name: 'Confirm' }).click();
        await expect(page.getByText('Review pending submissions')).toBeVisible();

        // Find and revoke the Germany sequence
        await search.ebolaSudan();
        await search.enableSearchFields('Author affiliations');
        await search.fill('Author affiliations', uuid);
        await search.select('Collection country', 'Germany');

        // Get the accession of the sequence to revoke
        await search.expectSequenceCount(1);
        const toRevokeLink = page.getByRole('link', { name: /LOC_[A-Z0-9]+\.1/ });
        const toRevokeId = await toRevokeLink.textContent();
        const toRevokeAccession = toRevokeId.split('.')[0];

        // Click on the sequence and revoke it (auto-approves on confirmation)
        await page.getByRole('cell', { name: 'Germany' }).click();
        await search.revokeSequence();

        // Release the revision (the revocation is already approved automatically)
        const reviewPage2 = new ReviewPage(page);
        await reviewPage2.navigateToReviewPage();
        await reviewPage2.waitForZeroProcessing();
        await reviewPage2.releaseAndGoToReleasedSequences();

        // Wait for the revised sequence to appear (with new date)
        while (!(await page.getByRole('cell', { name: '2023-06-15' }).isVisible())) {
            await page.reload();
            await page.waitForTimeout(2000);
        }

        // Now test all the banners
        const detailPage = new SequenceDetailPage(page);

        // Test 1: Revoked entry (v1 of revoked sequence) - should show revocation banner + not latest
        const revokedAccessionVersion = `${toRevokeAccession}.1`;
        await detailPage.goto(revokedAccessionVersion);
        await detailPage.expectRevocationBanner();
        await detailPage.expectNotLatestVersionBanner();

        // Test 2: Revocation entry (v2 of revoked sequence) - should show "This is a revocation version."
        const revocationAccessionVersion = `${toRevokeAccession}.2`;
        await detailPage.goto(revocationAccessionVersion);
        await detailPage.expectRevocationVersionBanner();

        // Test 3: Deprecated entry (v1 of revised sequence) - should show not latest version banner
        const deprecatedAccessionVersion = `${toReviseAccession}.1`;
        await detailPage.goto(deprecatedAccessionVersion);
        await detailPage.expectNotLatestVersionBanner();

        // Test 4: Revised entry (v2 of revised sequence) - should NOT show not latest version banner
        const revisedAccessionVersion = `${toReviseAccession}.2`;
        await detailPage.goto(revisedAccessionVersion);
        await detailPage.expectNoNotLatestVersionBanner();
    });

    test('can navigate to versions page and click deprecated version link', async ({
        page,
        groupId,
    }) => {
        test.setTimeout(300_000);
        void groupId;

        const uuid = randomUUID();
        const submissionPage = new SingleSequenceSubmissionPage(page);

        // Submit a sequence
        await page.goto('/');
        const reviewPage = await submissionPage.completeSubmission(
            {
                submissionId: 'for-versions',
                collectionCountry: 'Spain',
                collectionDate: '2023-03-01',
                authorAffiliations: uuid,
            },
            { main: TEST_SEQUENCE },
        );

        // Release the sequence
        await reviewPage.waitForZeroProcessing();
        await reviewPage.releaseAndGoToReleasedSequences();

        // Wait for sequence to appear
        while (!(await page.getByRole('link', { name: /LOC_/ }).isVisible())) {
            await page.reload();
            await page.waitForTimeout(2000);
        }

        // Get the accession
        const search = new SearchPage(page);
        await search.ebolaSudan();
        await search.enableSearchFields('Author affiliations');
        await search.fill('Author affiliations', uuid);

        const seqLink = page.getByRole('link', { name: /LOC_[A-Z0-9]+\.1/ });
        const seqId = await seqLink.textContent();
        const accession = seqId.split('.')[0];

        // Revise the sequence
        await page.getByRole('cell', { name: 'Spain' }).click();
        await search.reviseSequence();
        await page.getByLabel('Collection date').fill('2023-09-20');
        await page.getByRole('button', { name: /proceed to Approval/ }).click();
        await page.getByRole('button', { name: 'Confirm' }).click();

        // Release the revision
        const reviewPage2 = new ReviewPage(page);
        await reviewPage2.waitForZeroProcessing();
        await reviewPage2.releaseAndGoToReleasedSequences();

        // Wait for revised sequence
        while (!(await page.getByRole('cell', { name: '2023-09-20' }).isVisible())) {
            await page.reload();
            await page.waitForTimeout(2000);
        }

        // Navigate to the revised (latest) entry
        const detailPage = new SequenceDetailPage(page);
        const revisedAccessionVersion = `${accession}.2`;
        const deprecatedAccessionVersion = `${accession}.1`;

        await detailPage.goto(revisedAccessionVersion);

        // Navigate to versions page
        const versionsPage = await detailPage.gotoCompareVersions();

        // Verify versions page content
        await versionsPage.expectVersionsPageFor(accession);
        await versionsPage.expectLatestVersionLabel();
        await versionsPage.expectPreviousVersionLabel();

        // Verify the version diff view: with exactly two versions it auto-compares,
        // and the revised Collection date should show up as a changed field.
        await versionsPage.expectComparingVersions(1, 2);
        await versionsPage.expectFieldDiff('Collection date', '2023-03-01', '2023-09-20');
        await expect.poll(() => versionsPage.getCompareParam()).toBe('1,2');

        // "Hide unchanged fields" toggle: an unchanged field (Collection country = Spain) is
        // hidden by default and revealed once the toggle is disabled.
        await versionsPage.expectFieldRowAbsent('Collection country');
        await versionsPage.toggleHideUnchangedFields();
        await versionsPage.expectFieldRowVisible('Collection country', 'Spain');

        // Click on the deprecated version link
        await versionsPage.clickVersionLink(deprecatedAccessionVersion);

        // Verify we navigated to the deprecated version page
        await page.waitForURL(`**/seq/${deprecatedAccessionVersion}`);

        // Third revision: change the sequence (one SNP + one deletion) to produce a third
        // version that differs from version 2 in sequence-derived fields rather than metadata.
        const editPage = new EditPage(page);
        await editPage.goto('ebola-sudan', accession, 2);
        // Discard the existing segment; for a single-segment organism this removes the row and
        // reveals the "Add a segment" upload, which we use for the modified sequence.
        await page
            .getByTestId(/^discard_.+_segment_file$/)
            .first()
            .click();
        await page.getByTestId('Add a segment_segment_file').setInputFiles({
            name: 'revised.fasta',
            mimeType: 'text/plain',
            buffer: Buffer.from(`>revised\n${THIRD_REVISION_SEQUENCE}`),
        });
        const reviewPage3 = await editPage.submitChanges();
        await reviewPage3.waitForZeroProcessing();
        await reviewPage3.releaseAndGoToReleasedSequences();
        await search.waitForAccessionVersionInSearch(accession, 3);

        // Back to the versions page, now with three versions.
        await versionsPage.goto(accession);

        // With three versions, checkboxes appear and the diff defaults to the two most
        // recent versions (2 vs 3). These differ only in sequence, so a sequence-derived
        // field (Length) is shown as changed while the unchanged Collection date is hidden.
        await versionsPage.expectVersionSelectionAvailable();
        await versionsPage.expectComparingVersions(2, 3);
        await expect.poll(() => versionsPage.getCompareParam()).toBe('2,3');
        await versionsPage.expectFieldRowPresent('Length');
        await versionsPage.expectSharedMutationsHiddenByDefault();
        await versionsPage.expectNucleotideMutationDiff('Substitutions', null, 'A165G');
        await versionsPage.expectNucleotideMutationDiff('Deletions', null, '228-230');
        await versionsPage.expectFieldRowAbsent('Collection date');

        // Flip the selection to compare versions 1 and 2 (uncheck 3, check 1 - deterministic
        // regardless of which version the toggle drops). These differ in Collection date.
        await versionsPage.toggleVersionSelection(`${accession}.3`);
        await versionsPage.toggleVersionSelection(`${accession}.1`);
        await versionsPage.expectComparingVersions(1, 2);
        await versionsPage.expectFieldDiff('Collection date', '2023-03-01', '2023-09-20');
        await expect.poll(() => versionsPage.getCompareParam()).toBe('1,2');
    });
});
