import { assert, expect } from 'chai';
import clone from "clone";
import { describe, it } from 'mocha';
import { genericSourcePlayMatch, playsAreAddedOnly, playsAreBumpedOnly, playsAreSortConsistent } from "../../utils/PlayComparisonUtils.ts";
import { generatePlay, generatePlays } from "../../../core/tests/utils/PlayTestUtils.ts";
import type {PlayObject} from "../../../core/Atomic.ts";

const newPlay = generatePlay();

const existingList = generatePlays(10);

describe('Compare lists by order', function () {

    describe('Identity', function () {
        it('Identical lists are equal', function () {
            const identicalList = [...existingList.map(x => clone(x))];
            assert.isTrue(playsAreSortConsistent(existingList, identicalList));
        });

        it('Non-identical lists are not equal', function () {
            assert.isFalse(playsAreSortConsistent(existingList, generatePlays(11)));
        });

        it('Non-identical lists with modifications are not equal', function () {
            const modified = [...existingList.map(x => clone(x))];
            modified.splice(2, 1, generatePlay());
            modified[6].data.track = 'A CHANGE';
            modified.splice(8, 0, generatePlay());
            const modded = [...modified, generatePlay()];
            assert.isFalse(playsAreSortConsistent(existingList, modded));
        });
    });

    describe('Non-identical lists', function() {
        let candidateList: PlayObject[];

        before(function() {
            candidateList = generatePlays(10);
        });

        it('are not add only', function () {
            const [ok, diff, addType] = playsAreAddedOnly(existingList, candidateList)
            assert.isFalse(ok);
        });

        it('are not bump only', function () {
            const [ok, diff, addType] = playsAreAddedOnly(existingList, candidateList)
            assert.isFalse(ok);
        });
    });

    describe('Lists with only prepended additions', function() {
        let candidateList: PlayObject[];

        before(function() {
            candidateList = [generatePlay(), generatePlay(), ...existingList];
        });

        it('are add only', function () {
            const [ok, diff, addType] = playsAreAddedOnly(existingList, candidateList)
            assert.isTrue(ok);
            assert.equal(addType, 'prepend');
        });

        it('are not bump only', function () {
            const [ok, diff, addType] = playsAreBumpedOnly(existingList, candidateList)
            assert.isFalse(ok);
        });
    });

    describe('Lists with only appended additions', function() {
        let candidateList: PlayObject[];

        before(function() {
            candidateList = [...existingList, generatePlay(), generatePlay()];
        });

        it('are add only', function () {
            const [ok, diff, addType] = playsAreAddedOnly(existingList, candidateList)
            assert.isTrue(ok);
            assert.equal(addType, 'append');
        });

        it('are not bump only', function () {
            const [ok, diff, addType] = playsAreBumpedOnly(existingList, candidateList)
            assert.isFalse(ok);
        });
    
    });

    describe('Lists of fixed length with prepends', function() {
        let candidateList: PlayObject[];

        before(function() {
            candidateList = [generatePlay(), generatePlay(), ...existingList].slice(0, 9);
        });

        it('are add only', function () {
            const [ok, diff, addType] = playsAreAddedOnly(existingList, candidateList)
            assert.isTrue(ok);
            assert.equal(addType, 'prepend');
        });

        it('are not bump only', function () {
            const [ok, diff, addType] = playsAreBumpedOnly(existingList, candidateList)
            assert.isFalse(ok);
        });
    });


    describe('Lists with inserts', function() {
        let candidateList: PlayObject[],
        candidateList2: PlayObject[];

        before(function() {
            candidateList = [...existingList.map(x => clone(x))];
            candidateList.splice(4, 0, generatePlay())

            candidateList2 = [...existingList.map(x => clone(x))];
            candidateList2.splice(2, 0, generatePlay())
            candidateList2.splice(6, 0, generatePlay())
        });

        it('are not add only', function () {
            const [ok, diff, addType] = playsAreAddedOnly(existingList, candidateList)
            assert.isFalse(ok)

            const [ok2, diff2, addType2] = playsAreAddedOnly(existingList, candidateList2)
            assert.isFalse(ok2)
        });

        it('are not bump only', function () {
            const [ok, diff, addType] = playsAreBumpedOnly(existingList, candidateList)
            assert.isFalse(ok)

            const [ok2, diff2, addType2] = playsAreBumpedOnly(existingList, candidateList2)
            assert.isFalse(ok2)
        });
    
    });

    describe('Lists with inserts and prepends', function() {
        let candidateList: PlayObject[];

        before(function() {
            candidateList = [...existingList.map(x => clone(x))];
            candidateList.splice(2, 0, generatePlay())
            candidateList.splice(6, 0, generatePlay())
            candidateList = [generatePlay(), generatePlay(), ...candidateList]
        });

        it('are not add only', function () {
            const [ok, diff, addType] = playsAreAddedOnly(existingList, candidateList)
            assert.isFalse(ok);
        });

        it('are not bump only', function () {
            const [ok, diff, addType] = playsAreBumpedOnly(existingList, candidateList)
            assert.isFalse(ok);
        });
    
    });

    describe('Lists with inserts and appends', function() {
        let candidateList: PlayObject[];

        before(function() {
            candidateList = [...existingList.map(x => clone(x))];
            candidateList.splice(2, 0, generatePlay())
            candidateList.splice(6, 0, generatePlay())
            candidateList = [...candidateList, generatePlay(), generatePlay()]
        });

        it('are not add only', function () {
            const [ok, diff, addType] = playsAreAddedOnly(existingList, candidateList)
            assert.isFalse(ok);
        });

        it('are not bump only', function () {
            const [ok, diff, addType] = playsAreBumpedOnly(existingList, candidateList)
            assert.isFalse(ok);
        });
    });

    describe('Lists with inserts and appends and prepends', function() {
        let candidateList: PlayObject[];

        before(function() {
            candidateList = [...existingList.map(x => clone(x))];
            candidateList = [generatePlay(), generatePlay(), ...candidateList, generatePlay(), generatePlay()]
        });

        it('are not add only', function () {
            const [ok, diff, addType] = playsAreAddedOnly(existingList, candidateList)
            assert.isFalse(ok);
        });

        it('are not bump only', function () {
            const [ok, diff, addType] = playsAreBumpedOnly(existingList, candidateList)
            assert.isFalse(ok);
        });
    });

    describe('Lists with plays bumped-by-prepend', function() {
        let candidateList: PlayObject[];

        before(function() {
            candidateList = [...existingList.map(x => clone(x))];
            const bumped = candidateList[6];
            candidateList.splice(6, 1);
            candidateList.unshift(bumped);
        });

        it('are not add only', function () {
            const [ok, diff, addType] = playsAreAddedOnly(existingList, candidateList);
            assert.isFalse(ok);
        });

        it('are bump only', function () {
            const [ok, diff, addType] = playsAreBumpedOnly(existingList, candidateList);
            assert.isTrue(ok);
            assert.equal(addType, 'prepend');
        });
    
    });

    describe('Lists with plays bumped-by-append', function() {
        let candidateList: PlayObject[];

        before(function() {
            candidateList = [...existingList.map(x => clone(x))];
            const bumped = candidateList[6];
            candidateList.splice(6, 1);
            candidateList.push(bumped);
        });

        it('are not add only', function () {
            const [ok, diff, addType] = playsAreAddedOnly(existingList, candidateList);
            assert.isFalse(ok);
        });

        it('are bump only', function () {
            const [ok, diff, addType] = playsAreBumpedOnly(existingList, candidateList);
            assert.isTrue(ok);
            assert.equal(addType, 'append');
        });
    
    });

    describe('Source Play Comparisons', function() {

        describe('Generic Source Comparison', function() {

            it('matches identical plays', function() {
                expect(genericSourcePlayMatch(newPlay, newPlay)).to.be.true;
            });

            it('matches identical plays with close timestamps', function() {
                const closePlay = clone(newPlay);
                closePlay.data.playDate = closePlay.data.playDate.add(3, 's');
                expect(genericSourcePlayMatch(newPlay, closePlay)).to.be.true;
            });

            it('does not match unique plays', function() {
                expect(genericSourcePlayMatch(newPlay, generatePlay())).to.be.false;
            });

            it('does not match unique plays with exact timestamps', function() {
                const diffPlay = generatePlay();
                diffPlay.data.playDate = newPlay.data.playDate;
                expect(genericSourcePlayMatch(newPlay, diffPlay)).to.be.false;
            });

            it('does not match unique plays with close timestamps', function() {
                const diffPlay = generatePlay();
                diffPlay.data.playDate = newPlay.data.playDate.add(3, 's');
                expect(genericSourcePlayMatch(newPlay, diffPlay)).to.be.false;
            });
        });

    });
});
