import CommanderCommand   from '../../CommanderCommand.js';
import ora                from 'ora';
import SharedConfig       from '../../../shared/config/SharedConfig';
import AccessesHelper     from '../../../helpers/AccessesHelper.js';
import Assignment         from '../../../sharedByClients/models/Assignment.js';
import DojoBackendManager from '../../../managers/DojoBackendManager.js';
import Toolbox            from '../../../shared/helpers/Toolbox.js';
import * as Gitlab        from '@gitbeaker/rest';
import TextStyle          from '../../../types/TextStyle.js';
import Config             from '../../../config/Config';
import SharedSonarManager from '../../../shared/managers/SharedSonarManager';
import { Option }         from 'commander';


type CommandOptions = { name: string, language: string, template?: string, members_id?: Array<number>, members_username?: Array<string>, clone?: string | boolean, sonar?: boolean, sonarStrict: boolean, gate?: string, profile?: string[] }


class AssignmentCreateCommand extends CommanderCommand {
    protected commandName: string = 'create';

    private members!: Array<Gitlab.UserSchema> | undefined;
    private templateIdOrNamespace: string | null = null;
    private assignment!: Assignment;
    private sonar: boolean = false;
    private sonarGate: string | undefined = undefined;
    private sonarProfiles: string[] = [];

    protected defineCommand() {
        this.command
            .description('create a new repository for an assignment')
            .requiredOption('-n, --name <name>', 'name of the assignment')
            .requiredOption('-l, --language <string>', 'main programming language of the assignment')
            .option('-i, --members_id <ids...>', 'list of gitlab members ids (teaching staff) to add to the repository')
            .option('-u, --members_username <usernames...>', 'list of gitlab members username (teaching staff) to add to the repository')
            .option('-t, --template <string>', 'id or url of the template (http/s and ssh urls are possible)')
            .option('-c, --clone [string]', 'automatically clone the repository (SSH required) in the specified directory (this will create a subdirectory with the assignment name)')
            .action(this.commandAction.bind(this));

        if ( SharedConfig.sonar.enabled ) {
            this.command.requiredOption('-s, --sonar', 'add sonar to the code checking process for assignment and exercises')
                .requiredOption('-d, --no-sonar', 'disable sonar for the code checking process for assignment and exercises')
                .addOption(new Option('--sonar-strict', 'force the sonar gate to pass to validate the exercise results').default(false).implies({ sonar: true }))
                .addOption(new Option('-g, --gate <gate>', 'quality gate for sonar').implies({ sonar: true }))
                .addOption(new Option('-p, --profile <profile...>', 'quality profiles for sonar').default([]).implies({ sonar: true }));
        }
    }

    private async dataRetrieval(options: CommandOptions) {
        console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));

        await AccessesHelper.checkTeachingStaff();

        this.members = await Config.gitlabManager.fetchMembers(options);
        if ( !this.members ) {
            throw new Error();
        }

        if ( options.sonar ) {
            const assignmentGetSonarSpinner: ora.Ora = ora('Checking server sonar status').start();
            this.sonar = (SharedConfig.sonar.enabled ? options.sonar ?? false : false);
            if ( this.sonar && !(await DojoBackendManager.isSonarEnabled()) ) {
                assignmentGetSonarSpinner.fail(`Sonar is currently not supported by the server. Disable sonar integration or try again later.`);
                throw new Error();
            }
            assignmentGetSonarSpinner.succeed(`Sonar is supported by the server`);

            this.sonarGate = options.gate;
            this.sonarProfiles = options.profile ?? [];
        }

        const assignmentGetSpinner: ora.Ora = ora('Checking assignment name availability').start();
        if ( await DojoBackendManager.getAssignment(options.name) ) {
            assignmentGetSpinner.fail(`Assignment name "${ options.name }" is already taken. Please choose another one.`);
            throw new Error();
        }
        assignmentGetSpinner.succeed(`Assignment name "${ options.name }" is available`);

        // Dojo languages
        const languagesSpinner: ora.Ora = ora('Checking language support').start();
        const languages = await DojoBackendManager.getLanguages();
        if ( !languages.includes(options.language) ) {
            languagesSpinner.fail(`Language "${ options.language }" is not supported. Choose a supported language or "other"`);
            console.log('List of supported languages:');
            for ( const l of languages ) {
                console.log(`  - ${ l }`);
            }
            throw new Error();
        }
        languagesSpinner.succeed(`Language "${ options.language }" is supported`);

        if ( (this.sonarGate ?? '') !== '' || this.sonarProfiles.length > 0 ) {
            const qualitiesSpinner: ora.Ora = ora('Checking sonar qualities').start();

            // SonarQube quality gate and profiles
            const result = await DojoBackendManager.testSonarQualities(this.sonarGate ?? '', this.sonarProfiles);
            if ( !result.valid ) {
                const invalid = (result.badGate == undefined ? result.badProfiles : [ result.badGate, ...result.badProfiles ]);
                qualitiesSpinner.fail(`Invalid quality gate or profiles : ${ invalid }`);
                throw new Error();
            }

            qualitiesSpinner.succeed(`Quality gate and profiles are valid`);
        }

        // SonarQube languages
        if ( this.sonar ) {
            const sonarLang = await DojoBackendManager.getSonarLanguages();
            if ( !sonarLang.includes(SharedSonarManager.mapLanguage(options.language)) ) {
                languagesSpinner.fail(`Language "${ options.language }" is not supported with Sonar. Choose a supported language or disable sonar`);
                throw new Error();
            }
        }

        if ( options.template ) {
            this.templateIdOrNamespace = options.template;

            if ( Number.isNaN(Number(this.templateIdOrNamespace)) ) {
                this.templateIdOrNamespace = Toolbox.urlToPath(this.templateIdOrNamespace);
            }

            if ( !await DojoBackendManager.checkTemplateAccess(this.templateIdOrNamespace) ) {
                throw new Error();
            }
        }
    }

    private async createAssignment(options: CommandOptions) {
        console.log(TextStyle.BLOCK('Please wait while we are creating the assignment (approximately 10 seconds)...'));

        this.assignment = await DojoBackendManager.createAssignment(options.name, options.language, this.members!, this.templateIdOrNamespace, this.sonar, !options.sonarStrict, this.sonarGate, this.sonarProfiles);

        const oraInfo = (message: string) => {
            ora({
                    text  : message,
                    indent: 4
                }).start().info();
        };

        oraInfo(`${ TextStyle.LIST_ITEM_NAME('Name:') } ${ this.assignment.name }`);
        oraInfo(`${ TextStyle.LIST_ITEM_NAME('Web URL:') } ${ this.assignment.gitlabCreationInfo.web_url }`);
        oraInfo(`${ TextStyle.LIST_ITEM_NAME('HTTP Repo:') } ${ this.assignment.gitlabCreationInfo.http_url_to_repo }`);
        oraInfo(`${ TextStyle.LIST_ITEM_NAME('SSH Repo:') } ${ this.assignment.gitlabCreationInfo.ssh_url_to_repo }`);
        if ( this.assignment.useSonar ) {
            oraInfo(`${ TextStyle.LIST_ITEM_NAME('Sonar project:') } ${ SharedConfig.sonar.url }/dashboard?id=${ this.assignment.sonarKey }`);
        }
    }

    private async cloneRepository(options: CommandOptions) {
        if ( options.clone ) {
            console.log(TextStyle.BLOCK('Please wait while we are cloning the repository...'));

            await Config.gitlabManager.cloneRepository(options.clone, this.assignment.gitlabCreationInfo.ssh_url_to_repo, undefined, true, 0);
        }
    }

    protected async commandAction(options: CommandOptions): Promise<void> {
        try {
            await this.dataRetrieval(options);
            await this.createAssignment(options);
            await this.cloneRepository(options);
        } catch ( e ) { /* Do nothing */ }
    }
}


export default new AssignmentCreateCommand();