import * as fs from 'fs';
import * as mkdirp from 'mkdirp';
import * as rimraf from 'rimraf';

import { assert } from 'chai';

import { IFileData } from '../../../../src/interfaces/cli/IFileData';

import { SourceCodeReader } from '../../../../src/cli/utils/SourceCodeReader';

describe('SourceCodeReader', () => {
    const expectedError: RegExp = /Given input path must be a valid/;
    const fileContent: string = 'test';
    const tmpDirectoryPath: string = 'test/tmp';

    before(() => {
        mkdirp.sync(tmpDirectoryPath);
    });

    describe('readSourceCode', () => {
        describe('Variant #1: input path is a file path', () => {
            describe('Variant #1: `inputPath` is a valid path', () => {
                const tmpFileName: string = 'test.js';
                const inputPath: string = `${tmpDirectoryPath}/${tmpFileName}`;
                const expectedFilesData: IFileData[] = [{
                    content: fileContent,
                    filePath: inputPath
                }];

                let filesData: IFileData[];

                before(() => {
                    fs.writeFileSync(inputPath, fileContent);
                    filesData = new SourceCodeReader(inputPath, {}).readSourceCode();
                });

                it('should return valid files data', () => {
                    assert.deepEqual(filesData, expectedFilesData);
                });

                after(() => {
                    fs.unlinkSync(inputPath);
                });
            });

            describe('Variant #2: `inputPath` is not a valid path', () => {
                const tmpFileName: string = 'test.js';
                const inputPath: string = `${tmpDirectoryPath}/${tmpFileName}`;

                let testFunc: () => void;

                before(() => {
                    testFunc = () => new SourceCodeReader(inputPath, {}).readSourceCode();
                });

                it('should throw an error if `inputPath` is not a valid path', () => {
                    assert.throws(testFunc, expectedError);
                });
            });

            describe('Variant #3: `inputPath` has invalid extension', () => {
                const tmpFileName: string = 'test.ts';
                const inputPath: string = `${tmpDirectoryPath}/${tmpFileName}`;

                let testFunc: () => void;

                before(() => {
                    fs.writeFileSync(inputPath, fileContent);
                    testFunc = () => new SourceCodeReader(inputPath, {}).readSourceCode();
                });

                it('should throw an error if `inputPath` has invalid extension', () => {
                    assert.throws(testFunc, expectedError);
                });

                after(() => {
                    fs.unlinkSync(inputPath);
                });
            });

            describe('Variant #4: `exclude` option', () => {
                describe('Variant #1: `inputPath` isn\'t excluded path', () => {
                    const tmpFileName: string = 'test.js';
                    const inputPath: string = `${tmpDirectoryPath}/${tmpFileName}`;
                    const expectedFilesData: IFileData[] = [{
                        content: fileContent,
                        filePath: inputPath
                    }];

                    let filesData: IFileData[];

                    before(() => {
                        fs.writeFileSync(inputPath, fileContent);
                        filesData = new SourceCodeReader(
                            inputPath,
                            {
                                exclude: ['**/foo.js']
                            }
                        ).readSourceCode();
                    });

                    it('should return valid files data', () => {
                        assert.deepEqual(filesData, expectedFilesData);
                    });

                    after(() => {
                        fs.unlinkSync(inputPath);
                    });
                });

                describe('Variant #2: `inputPath` is excluded path', () => {
                    describe('Variant #1: exclude by `glob` pattern', () => {
                        const tmpFileName: string = 'test.js';
                        const inputPath: string = `${tmpDirectoryPath}/${tmpFileName}`;

                        let testFunc: () => void;

                        before(() => {
                            fs.writeFileSync(inputPath, fileContent);
                            testFunc = () => new SourceCodeReader(
                                inputPath,
                                {
                                    exclude: [`**/${tmpFileName}`]
                                }
                            ).readSourceCode();
                        });

                        it('should throw an error if `inputPath` is the excluded file path', () => {
                            assert.throws(testFunc, expectedError);
                        });

                        after(() => {
                            fs.unlinkSync(inputPath);
                        });
                    });

                    describe('Variant #2: exclude by file name', () => {
                        const tmpFileName: string = 'test.js';
                        const inputPath: string = `${tmpDirectoryPath}/${tmpFileName}`;

                        let testFunc: () => void;

                        before(() => {
                            fs.writeFileSync(inputPath, fileContent);
                            testFunc = () => new SourceCodeReader(
                                inputPath,
                                {
                                    exclude: [tmpFileName]
                                }
                            ).readSourceCode();
                        });

                        it('should throw an error if `inputPath` is the excluded file path', () => {
                            assert.throws(testFunc, expectedError);
                        });

                        after(() => {
                            fs.unlinkSync(inputPath);
                        });
                    });

                    describe('Variant #3: exclude by file path', () => {
                        const tmpFileName: string = 'test.js';
                        const inputPath: string = `${tmpDirectoryPath}/${tmpFileName}`;

                        let testFunc: () => void;

                        before(() => {
                            fs.writeFileSync(inputPath, fileContent);
                            testFunc = () => new SourceCodeReader(
                                inputPath,
                                {
                                    exclude: [inputPath]
                                }
                            ).readSourceCode();
                        });

                        it('should throw an error if `inputPath` is the excluded file path', () => {
                            assert.throws(testFunc, expectedError);
                        });

                        after(() => {
                            fs.unlinkSync(inputPath);
                        });
                    });
                });
            });
        });

        describe('Variant #2: input path is a directory path', () => {
            describe('Variant #1: `inputPath` is a valid path', () => {
                const tmpFileName1: string = 'foo.js';
                const tmpFileName2: string = 'bar.js';
                const tmpFileName3: string = 'baz.png';
                const tmpFileName4: string = 'bark-obfuscated.js';
                const filePath1: string = `${tmpDirectoryPath}/${tmpFileName1}`;
                const filePath2: string = `${tmpDirectoryPath}/${tmpFileName2}`;
                const filePath3: string = `${tmpDirectoryPath}/${tmpFileName3}`;
                const filePath4: string = `${tmpDirectoryPath}/${tmpFileName4}`;

                const expectedResult: IFileData[] = [
                    {
                        filePath: filePath2,
                        content: fileContent
                    },
                    {
                        filePath: filePath1,
                        content: fileContent
                    }
                ];

                let result: IFileData[];

                before(() => {
                    fs.writeFileSync(filePath1, fileContent);
                    fs.writeFileSync(filePath2, fileContent);
                    fs.writeFileSync(filePath3, fileContent);
                    fs.writeFileSync(filePath4, fileContent);
                    result = new SourceCodeReader(tmpDirectoryPath, {}).readSourceCode();
                });

                it('should return files data', () => {
                    assert.deepEqual(result, expectedResult);
                });

                after(() => {
                    fs.unlinkSync(filePath1);
                    fs.unlinkSync(filePath2);
                    fs.unlinkSync(filePath3);
                    fs.unlinkSync(filePath4);
                });
            });

            describe('Variant #2: `inputPath` is not a valid path', () => {
                const inputPath: string = 'abc';

                let testFunc: () => void;

                before(() => {
                    testFunc = () => new SourceCodeReader(inputPath, {}).readSourceCode();
                });

                it('should throw an error if `inputPath` is not a valid path', () => {
                    assert.throws(testFunc, expectedError);
                });
            });

            describe('Variant #3: `inputPath` is a directory with sub-directories', () => {
                const parentDirectoryName1: string = 'parent1';
                const parentDirectoryName2: string = 'parent';
                const parentDirectoryPath1: string = `${tmpDirectoryPath}/${parentDirectoryName1}`;
                const parentDirectoryPath2: string = `${tmpDirectoryPath}/${parentDirectoryName2}`;
                const tmpFileName1: string = 'foo.js';
                const tmpFileName2: string = 'bar.js';
                const tmpFileName3: string = 'baz.js';
                const tmpFileName4: string = 'bark.js';
                const filePath1: string = `${tmpDirectoryPath}/${tmpFileName1}`;
                const filePath2: string = `${tmpDirectoryPath}/${tmpFileName2}`;
                const filePath3: string = `${parentDirectoryPath1}/${tmpFileName3}`;
                const filePath4: string = `${parentDirectoryPath2}/${tmpFileName4}`;

                const expectedResult: IFileData[] = [
                    {
                        filePath: filePath2,
                        content: fileContent
                    },
                    {
                        filePath: filePath1,
                        content: fileContent
                    },
                    {
                        filePath: filePath4,
                        content: fileContent
                    },
                    {
                        filePath: filePath3,
                        content: fileContent
                    }
                ];

                let result: IFileData[];

                before(() => {
                    mkdirp.sync(parentDirectoryPath1);
                    mkdirp.sync(parentDirectoryPath2);
                    fs.writeFileSync(filePath1, fileContent);
                    fs.writeFileSync(filePath2, fileContent);
                    fs.writeFileSync(filePath3, fileContent);
                    fs.writeFileSync(filePath4, fileContent);
                    result = new SourceCodeReader(tmpDirectoryPath, {}).readSourceCode();
                });

                it('should return files data', () => {
                    assert.deepEqual(result, expectedResult);
                });

                after(() => {
                    fs.unlinkSync(filePath1);
                    fs.unlinkSync(filePath2);
                    fs.unlinkSync(filePath3);
                    fs.unlinkSync(filePath4);
                    rimraf.sync(parentDirectoryPath1);
                    rimraf.sync(parentDirectoryPath2);
                });
            });

            describe('Variant #4: `exclude` option', () => {
                describe('Variant #1: `inputPath` isn\'t excluded path', () => {
                    const tmpFileName1: string = 'foo.js';
                    const tmpFileName2: string = 'bar.js';
                    const tmpFileName3: string = 'baz.png';
                    const tmpFileName4: string = 'bark-obfuscated.js';
                    const filePath1: string = `${tmpDirectoryPath}/${tmpFileName1}`;
                    const filePath2: string = `${tmpDirectoryPath}/${tmpFileName2}`;
                    const filePath3: string = `${tmpDirectoryPath}/${tmpFileName3}`;
                    const filePath4: string = `${tmpDirectoryPath}/${tmpFileName4}`;

                    const expectedResult: IFileData[] = [
                        {
                            filePath: filePath2,
                            content: fileContent
                        },
                        {
                            filePath: filePath1,
                            content: fileContent
                        }
                    ];

                    let result: IFileData[];

                    before(() => {
                        fs.writeFileSync(filePath1, fileContent);
                        fs.writeFileSync(filePath2, fileContent);
                        fs.writeFileSync(filePath3, fileContent);
                        fs.writeFileSync(filePath4, fileContent);
                        result = new SourceCodeReader(
                            tmpDirectoryPath,
                            {
                                exclude: ['**/hawk.js']
                            }
                        ).readSourceCode();
                    });

                    it('should return files data', () => {
                        assert.deepEqual(result, expectedResult);
                    });

                    after(() => {
                        fs.unlinkSync(filePath1);
                        fs.unlinkSync(filePath2);
                        fs.unlinkSync(filePath3);
                        fs.unlinkSync(filePath4);
                    });
                });

                describe('Variant #2: `inputPath` is excluded path', () => {
                    describe('Variant #1: exclude by `glob` pattern', () => {
                        const tmpFileName1: string = 'foo.js';
                        const tmpFileName2: string = 'bar.js';
                        const tmpFileName3: string = 'baz.js';
                        const tmpFileName4: string = 'bark.js';
                        const filePath1: string = `${tmpDirectoryPath}/${tmpFileName1}`;
                        const filePath2: string = `${tmpDirectoryPath}/${tmpFileName2}`;
                        const filePath3: string = `${tmpDirectoryPath}/${tmpFileName3}`;
                        const filePath4: string = `${tmpDirectoryPath}/${tmpFileName4}`;

                        const expectedResult: IFileData[] = [
                            {
                                filePath: filePath3,
                                content: fileContent
                            },
                            {
                                filePath: filePath1,
                                content: fileContent
                            }
                        ];

                        let result: IFileData[];

                        before(() => {
                            fs.writeFileSync(filePath1, fileContent);
                            fs.writeFileSync(filePath2, fileContent);
                            fs.writeFileSync(filePath3, fileContent);
                            fs.writeFileSync(filePath4, fileContent);
                            result = new SourceCodeReader(
                                tmpDirectoryPath,
                                {
                                    exclude: [
                                        `**/${tmpFileName2}`,
                                        `**/${tmpFileName4}`
                                    ]
                                }
                            ).readSourceCode();
                        });

                        it('should return files data', () => {
                            assert.deepEqual(result, expectedResult);
                        });

                        after(() => {
                            fs.unlinkSync(filePath1);
                            fs.unlinkSync(filePath2);
                            fs.unlinkSync(filePath3);
                            fs.unlinkSync(filePath4);
                        });
                    });

                    describe('Variant #2: exclude by file name', () => {
                        const tmpFileName1: string = 'foo.js';
                        const tmpFileName2: string = 'bar.js';
                        const tmpFileName3: string = 'baz.js';
                        const tmpFileName4: string = 'bark.js';
                        const filePath1: string = `${tmpDirectoryPath}/${tmpFileName1}`;
                        const filePath2: string = `${tmpDirectoryPath}/${tmpFileName2}`;
                        const filePath3: string = `${tmpDirectoryPath}/${tmpFileName3}`;
                        const filePath4: string = `${tmpDirectoryPath}/${tmpFileName4}`;

                        const expectedResult: IFileData[] = [
                            {
                                filePath: filePath3,
                                content: fileContent
                            },
                            {
                                filePath: filePath1,
                                content: fileContent
                            }
                        ];

                        let result: IFileData[];

                        before(() => {
                            fs.writeFileSync(filePath1, fileContent);
                            fs.writeFileSync(filePath2, fileContent);
                            fs.writeFileSync(filePath3, fileContent);
                            fs.writeFileSync(filePath4, fileContent);
                            result = new SourceCodeReader(
                                tmpDirectoryPath,
                                {
                                    exclude: [
                                        tmpFileName2,
                                        tmpFileName4
                                    ]
                                }
                            ).readSourceCode();
                        });

                        it('should return files data', () => {
                            assert.deepEqual(result, expectedResult);
                        });

                        after(() => {
                            fs.unlinkSync(filePath1);
                            fs.unlinkSync(filePath2);
                            fs.unlinkSync(filePath3);
                            fs.unlinkSync(filePath4);
                        });
                    });

                    describe('Variant #3: exclude by file path', () => {
                        const tmpFileName1: string = 'foo.js';
                        const tmpFileName2: string = 'bar.js';
                        const tmpFileName3: string = 'baz.js';
                        const tmpFileName4: string = 'bark.js';
                        const filePath1: string = `${tmpDirectoryPath}/${tmpFileName1}`;
                        const filePath2: string = `${tmpDirectoryPath}/${tmpFileName2}`;
                        const filePath3: string = `${tmpDirectoryPath}/${tmpFileName3}`;
                        const filePath4: string = `${tmpDirectoryPath}/${tmpFileName4}`;

                        const expectedResult: IFileData[] = [
                            {
                                filePath: filePath3,
                                content: fileContent
                            },
                            {
                                filePath: filePath1,
                                content: fileContent
                            }
                        ];

                        let result: IFileData[];

                        before(() => {
                            fs.writeFileSync(filePath1, fileContent);
                            fs.writeFileSync(filePath2, fileContent);
                            fs.writeFileSync(filePath3, fileContent);
                            fs.writeFileSync(filePath4, fileContent);
                            result = new SourceCodeReader(
                                tmpDirectoryPath,
                                {
                                    exclude: [
                                        filePath2,
                                        filePath4
                                    ]
                                }
                            ).readSourceCode();
                        });

                        it('should return files data', () => {
                            assert.deepEqual(result, expectedResult);
                        });

                        after(() => {
                            fs.unlinkSync(filePath1);
                            fs.unlinkSync(filePath2);
                            fs.unlinkSync(filePath3);
                            fs.unlinkSync(filePath4);
                        });
                    });

                    describe('Variant #4: exclude whole directory', () => {
                        const tmpFileName1: string = 'foo.js';
                        const tmpFileName2: string = 'bar.js';
                        const tmpFileName3: string = 'baz.js';
                        const tmpFileName4: string = 'bark.js';
                        const filePath1: string = `${tmpDirectoryPath}/${tmpFileName1}`;
                        const filePath2: string = `${tmpDirectoryPath}/${tmpFileName2}`;
                        const filePath3: string = `${tmpDirectoryPath}/${tmpFileName3}`;
                        const filePath4: string = `${tmpDirectoryPath}/${tmpFileName4}`;

                        let testFunc: () => void;

                        before(() => {
                            fs.writeFileSync(filePath1, fileContent);
                            fs.writeFileSync(filePath2, fileContent);
                            fs.writeFileSync(filePath3, fileContent);
                            fs.writeFileSync(filePath4, fileContent);
                            testFunc = () => new SourceCodeReader(
                                tmpDirectoryPath,
                                {
                                    exclude: [tmpDirectoryPath]
                                }
                            ).readSourceCode();
                        });

                        it('should return files data', () => {
                            assert.throws(testFunc, expectedError);
                        });

                        after(() => {
                            fs.unlinkSync(filePath1);
                            fs.unlinkSync(filePath2);
                            fs.unlinkSync(filePath3);
                            fs.unlinkSync(filePath4);
                        });
                    });
                });
            });

            describe('Variant #5: `inputPath` is a valid path with dot', () => {
                const tmpDirectoryWithDotPath: string = `${tmpDirectoryPath}.bar`;
                const tmpFileName: string = 'foo.js';
                const filePath: string = `${tmpDirectoryWithDotPath}/${tmpFileName}`;

                const expectedResult: IFileData[] = [
                    {
                        filePath: filePath,
                        content: fileContent
                    }
                ];

                let result: IFileData[];

                before(() => {
                    mkdirp.sync(tmpDirectoryWithDotPath);
                    fs.writeFileSync(filePath, fileContent);
                    result = new SourceCodeReader(tmpDirectoryWithDotPath, {}).readSourceCode();
                });

                it('should return files data', () => {
                    assert.deepEqual(result, expectedResult);
                });

                after(() => {
                    fs.unlinkSync(filePath);
                    rimraf.sync(tmpDirectoryWithDotPath);
                });
            });
        });
    });

    after(() => {
        rimraf.sync(tmpDirectoryPath);
    });
});
