import { Express }                    from 'express-serve-static-core';
import express                        from 'express';
import * as ExpressValidator          from 'express-validator';
import { StatusCodes }                from 'http-status-codes';
import RoutesManager                  from '../express/RoutesManager';
import ParamsValidatorMiddleware      from '../middlewares/ParamsValidatorMiddleware';
import SecurityMiddleware             from '../middlewares/SecurityMiddleware';
import SecurityCheckType              from '../types/SecurityCheckType';
import GitlabUser                     from '../shared/types/Gitlab/GitlabUser';
import GitlabManager                  from '../managers/GitlabManager';
import Config                         from '../config/Config';
import GitlabMember                   from '../shared/types/Gitlab/GitlabMember';
import GitlabAccessLevel              from '../shared/types/Gitlab/GitlabAccessLevel';
import GitlabRepository               from '../shared/types/Gitlab/GitlabRepository';
import { AxiosError, HttpStatusCode } from 'axios';
import logger                         from '../shared/logging/WinstonLogger';
import DojoValidators                 from '../helpers/DojoValidators';
import { Prisma }                     from '@prisma/client';
import db                             from '../helpers/DatabaseHelper';
import { Assignment }                 from '../types/DatabaseTypes';
import AssignmentManager              from '../managers/AssignmentManager';
import GitlabVisibility               from '../shared/types/Gitlab/GitlabVisibility';
import fs                             from 'fs';
import path                           from 'path';
import SharedAssignmentHelper         from '../shared/helpers/Dojo/SharedAssignmentHelper';


class AssignmentRoutes implements RoutesManager {
    private readonly assignmentValidator: ExpressValidator.Schema = {
        name    : {
            trim    : true,
            notEmpty: true
        },
        members : {
            trim           : true,
            notEmpty       : true,
            customSanitizer: DojoValidators.jsonSanitizer
        },
        template: {
            trim           : true,
            custom         : DojoValidators.templateUrlValidator,
            customSanitizer: DojoValidators.templateUrlSanitizer
        }
    };

    registerOnBackend(backend: Express) {
        backend.get('/assignments/:assignmentNameOrUrl', SecurityMiddleware.check(true), this.getAssignment);
        backend.post('/assignments', SecurityMiddleware.check(true, SecurityCheckType.TEACHING_STAFF), ParamsValidatorMiddleware.validate(this.assignmentValidator), this.createAssignment);

        backend.patch('/assignments/:assignmentNameOrUrl/publish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(true));
        backend.patch('/assignments/:assignmentNameOrUrl/unpublish', SecurityMiddleware.check(true, SecurityCheckType.ASSIGNMENT_STAFF), this.changeAssignmentPublishedStatus(false));
    }

    // Get an assignment by its name or gitlab url
    private async getAssignment(req: express.Request, res: express.Response) {
        const assignment: Assignment | undefined = req.boundParams.assignment;

        if ( assignment && !assignment.published && !await AssignmentManager.isUserAllowedToAccessAssignment(assignment, req.session.profile) ) {
            // @ts-ignore
            delete assignment.gitlabId;
            // @ts-ignore
            delete assignment.gitlabLink;
            // @ts-ignore
            delete assignment.gitlabCreationInfo;
            // @ts-ignore
            delete assignment.gitlabLastInfo;
            // @ts-ignore
            delete assignment.gitlabLastInfoDate;
            // @ts-ignore
            delete assignment.staff;
            // @ts-ignore
            delete assignment.exercises;
        }

        return assignment ? req.session.sendResponse(res, StatusCodes.OK, assignment) : res.status(StatusCodes.NOT_FOUND).send();
    }

    private async createAssignment(req: express.Request, res: express.Response) {
        const params: {
            name: string, members: Array<GitlabUser>, template: string
        } = req.body;
        params.members = [ await req.session.profile.gitlabProfile.value, ...params.members ];
        params.members = params.members.removeObjectDuplicates(gitlabUser => gitlabUser.id);


        let repository: GitlabRepository;
        try {
            repository = await GitlabManager.createRepository(params.name, Config.assignment.default.description.replace('{{ASSIGNMENT_NAME}}', params.name), Config.assignment.default.visibility, Config.assignment.default.initReadme, Config.gitlab.group.assignments, Config.assignment.default.sharedRunnersEnabled, Config.assignment.default.wikiEnabled, params.template);

            await GitlabManager.protectBranch(repository.id, '*', true, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.DEVELOPER, GitlabAccessLevel.OWNER);

            await GitlabManager.addRepositoryBadge(repository.id, Config.gitlab.badges.pipeline.link, Config.gitlab.badges.pipeline.imageUrl, 'Pipeline Status');
        } catch ( error ) {
            if ( error instanceof AxiosError ) {
                if ( error.response?.data.message.name && error.response.data.message.name == 'has already been taken' ) {
                    return res.status(StatusCodes.CONFLICT).send();
                }

                return res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send();
            }

            logger.error(error);
            return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send();
        }

        try {
            await GitlabManager.createFile(repository.id, '.gitlab-ci.yml', fs.readFileSync(path.join(__dirname, '../../assets/assignment_gitlab_ci.yml'), 'base64'), 'Add .gitlab-ci.yml (DO NOT MODIFY THIS FILE)');
        } catch ( error ) {
            logger.error(error);

            if ( error instanceof AxiosError ) {
                return res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send();
            }

            return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send();
        }

        try {
            await Promise.all(params.members.map(member => member.id).map(async (memberId: number): Promise<GitlabMember | false> => {
                try {
                    return await GitlabManager.addRepositoryMember(repository.id, memberId, GitlabAccessLevel.DEVELOPER);
                } catch ( e ) {
                    return false;
                }
            }));

            const assignment: Assignment = await db.assignment.create({
                                                                          data: {
                                                                              name              : repository.name,
                                                                              gitlabId          : repository.id,
                                                                              gitlabLink        : repository.web_url,
                                                                              gitlabCreationInfo: repository as unknown as Prisma.JsonObject,
                                                                              gitlabLastInfo    : repository as unknown as Prisma.JsonObject,
                                                                              gitlabLastInfoDate: new Date(),
                                                                              staff             : {
                                                                                  connectOrCreate: [ ...params.members.map(gitlabUser => {
                                                                                      return {
                                                                                          create: {
                                                                                              id            : gitlabUser.id,
                                                                                              gitlabUsername: gitlabUser.name
                                                                                          },
                                                                                          where : {
                                                                                              id: gitlabUser.id
                                                                                          }
                                                                                      };
                                                                                  }) ]
                                                                              }
                                                                          }
                                                                      }) as unknown as Assignment;

            return req.session.sendResponse(res, StatusCodes.OK, assignment);
        } catch ( error ) {
            if ( error instanceof AxiosError ) {
                return res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send();
            }

            logger.error(error);
            return res.status(StatusCodes.INTERNAL_SERVER_ERROR).send();
        }
    }

    private changeAssignmentPublishedStatus(publish: boolean): (req: express.Request, res: express.Response) => Promise<void> {
        return async (req: express.Request, res: express.Response): Promise<void> => {
            if ( publish ) {
                const isPublishable = await SharedAssignmentHelper.isPublishable(req.boundParams.assignment!.gitlabId);
                if ( !isPublishable.isPublishable ) {
                    return req.session.sendResponse(res, StatusCodes.BAD_REQUEST, { lastPipeline: isPublishable.lastPipeline }, isPublishable.status?.message, isPublishable.status?.code);
                }
            }

            try {
                await GitlabManager.changeRepositoryVisibility(req.boundParams.assignment!.gitlabId, publish ? GitlabVisibility.INTERNAL : GitlabVisibility.PRIVATE);

                await db.assignment.update({
                                               where: {
                                                   name: req.boundParams.assignment!.name
                                               },
                                               data : {
                                                   published: publish
                                               }
                                           });

                req.session.sendResponse(res, StatusCodes.OK);
            } catch ( error ) {
                if ( error instanceof AxiosError ) {
                    res.status(error.response?.status ?? HttpStatusCode.InternalServerError).send();
                    return;
                }

                logger.error(error);
                res.status(StatusCodes.INTERNAL_SERVER_ERROR).send();
            }
        };
    }

}


export default new AssignmentRoutes();