import 'mocha';
import { expect } from 'chai';
import { ScriptCode } from '@/domain/ScriptCode';
import { IScriptCode } from '@/domain/IScriptCode';
import { ILanguageSyntax } from '@/domain/ScriptCode';
import { LanguageSyntaxStub } from '@tests/unit/stubs/LanguageSyntaxStub';

describe('ScriptCode', () => {
    describe('code', () => {
        describe('throws with invalid code', () => {
            // arrange
            const testCases = [
                {
                    name: 'throws when "execute" and "revert" are same',
                    code: {
                        execute: 'same',
                        revert: 'same',
                    },
                    expectedError: `(revert): Code itself and its reverting code cannot be the same`,
                },
                {
                    name: 'cannot construct with undefined "execute"',
                    code: {
                        execute: undefined,
                        revert: 'code',
                    },
                    expectedError: `code is empty or undefined`,
                },
                {
                    name: 'cannot construct with empty "execute"',
                    code: {
                        execute: '',
                        revert: 'code',
                    },
                    expectedError: `code is empty or undefined`,
                },
            ];
            for (const testCase of testCases) {
                it(testCase.name, () => {
                    // act
                    const act = () => new ScriptCodeBuilder()
                        .withExecute( testCase.code.execute)
                        .withRevert(testCase.code.revert)
                        .build();
                    // assert
                    expect(act).to.throw(testCase.expectedError);
                });
            }
        });
        describe('throws with invalid code in both "execute" or "revert"', () => {
            // arrange
            const testCases = [
                {
                    testName: 'cannot construct with duplicate lines',
                    code: 'duplicate\nduplicate\ntest\nduplicate',
                    expectedMessage: 'Duplicates detected in script:\n(0) - duplicate\n(1) - duplicate',
                },
                {
                    testName: 'cannot construct with empty lines',
                    code: 'line1\n\n\nline2',
                    expectedMessage: 'script has empty lines',
                },
            ];
            // act
            const actions = [];
            for (const testCase of testCases) {
                actions.push(...[
                    {
                        act: () => new ScriptCodeBuilder()
                            .withExecute(testCase.code)
                            .build(),
                        testName: `execute: ${testCase.testName}`,
                        expectedMessage: testCase.expectedMessage,
                    },
                    {
                        act: () => new ScriptCodeBuilder()
                            .withRevert(testCase.code)
                            .build(),
                        testName: `revert: ${testCase.testName}`,
                        expectedMessage: `(revert): ${testCase.expectedMessage}`,
                    },
                ]);
            }
            // assert
            for (const action of actions) {
                it(action.testName, () => {
                    expect(action.act).to.throw(action.expectedMessage,
                        `Code used: ${action.code}`);
                });
            }
        });
        describe('sets as expected with valid "execute" or "revert"', () => {
            // arrange
            const syntax = new LanguageSyntaxStub()
                .withCommonCodeParts(')', 'else', '(')
                .withCommentDelimiters('#', '//');
            const testCases = [
                {
                    testName: 'code is a valid string',
                    code: 'valid code',
                },
                {
                    testName: 'code consists of common code parts',
                    code: syntax.commonCodeParts.join(' '),
                },
                {
                    testName: 'code is a common code part',
                    code: syntax.commonCodeParts[0],
                },
                {
                    testName: `code with duplicated comment lines (${syntax.commentDelimiters[0]})`,
                    code: `${syntax.commentDelimiters[0]} comment\n${syntax.commentDelimiters[0]} comment`,
                },
                {
                    testName: `code with duplicated comment lines (${syntax.commentDelimiters[1]})`,
                    code: `${syntax.commentDelimiters[1]} comment\n${syntax.commentDelimiters[1]} comment`,
                },
            ];
            // act
            const actions = [];
            for (const testCase of testCases) {
                actions.push(...[
                    {
                        testName: `execute: ${testCase.testName}`,
                        act: () =>
                            new ScriptCodeBuilder()
                                .withSyntax(syntax)
                                .withExecute(testCase.code)
                                .build(),
                        expect: (sut: IScriptCode) => sut.execute === testCase.code,
                    },
                    {
                        testName: `revert: ${testCase.testName}`,
                        act: () =>
                            new ScriptCodeBuilder()
                                .withSyntax(syntax)
                                .withRevert(testCase.code)
                                .build(),
                        expect: (sut: IScriptCode) => sut.revert === testCase.code,
                    },
                ]);
            }
            // assert
            for (const action of actions) {
                it(action.testName, () => {
                    const sut = action.act();
                    expect(action.expect(sut));
                });
            }
        });
    });
    describe('syntax', () => {
        it('throws if undefined', () => {
            // arrange
            const expectedError = 'undefined syntax';
            const syntax = undefined;
            // act
            const act = () => new ScriptCodeBuilder()
                .withSyntax(syntax)
                .build();
            // assert
            expect(act).to.throw(expectedError);
        });
    });
});

class ScriptCodeBuilder {
    public execute = 'default-execute-code';
    public revert = '';
    public syntax: ILanguageSyntax = new LanguageSyntaxStub();

    public withExecute(execute: string) {
        this.execute = execute;
        return this;
    }
    public withRevert(revert: string) {
        this.revert = revert;
        return this;
    }
    public withSyntax(syntax: ILanguageSyntax) {
        this.syntax = syntax;
        return this;
    }

    public build(): ScriptCode {
        return new ScriptCode(
            this.execute,
            this.revert,
            this.syntax);
    }
}
