import { Injectable, Logger } from "@nestjs/common";
import { Request } from "express";
import { CommonListDto } from "../../dto/common.dto";
import ResponseDto from "../../dto/response.dto";
import { EntityManager } from "typeorm";
import CategoryEntity from "../../entity/category.entity";
import AttributeEntity from "../../entity/attribute.entity";
import * as bcrypt from 'bcrypt';
import RateEntity from "../../entity/rate.entity";
import ArticleEntity from "../../entity/article.entity";
import InvoiceCounterEntity from "../../entity/invoice_counter.entity";
import CreditNoteCounterEntity from "../../entity/credit_note.entity";

@Injectable()
export default class CommonProvider {
    
    private logger: Logger = new Logger(CommonProvider.name);
    
    constructor(private entityManager: EntityManager){}

    /**
     * This metod is used for listing simple tables
     * Tables with +7k, 8k record is risky use this method
     * @param request 
     * @param payload 
     * @returns 
     */
    async list(payload: CommonListDto, search?: string): Promise<ResponseDto>{

        const response: ResponseDto = new ResponseDto();

        try{

            const result = this.entityManager.dataSource.entityMetadatas.find(m => m.tableName == payload.entity);
            if(!result){
                throw new Error("Target not found");
            }

            let relations: any | undefined = undefined;
            if(payload.joins && payload.joins.length > 0){
                relations = {};
                payload.joins.forEach(j => {
                    relations[j] = true;
                });
            }

            let where: any | undefined = undefined;
            if(payload.filterId){
                where = {id: payload.filterId};
            }
            
            if(payload.joinsWhere && payload.joinsWhere.length > 0){
                if(!where){
                    where = {};
                }
                payload.joinsWhere.forEach(jw => {
                    where[jw.entity!] = {id: jw.filterIdRelation};
                });
            }

            let sortObject: any = {"id": "DESC"};
            if(payload.sortBy && payload.sortSide){
                delete sortObject.id;
                sortObject[payload.sortBy] = payload.sortSide;
            }

            const repository = this.entityManager.dataSource.getRepository(result.target);
            let items: any[] = await repository.find(
                                    {
                                        where       : where,
                                        order       : sortObject,
                                        relations   : relations
                                    }
                                );

            // Sanitize!
            for (const item of items){
                delete item.password;
                if(payload.joins && payload.joins.length > 0){
                    payload.joins.forEach(j => {
                        if(item[j]){
                            delete item[j].password;
                        }
                    });
                }
            }

            if(search){
                const newItems: any[] = [];
                items.forEach(i => {
                    let contains = false;
                    const keys: string[] = Object.keys(i);
                    for(let k = 0; k < keys.length; k++){
                        const keyName = keys[k];
                        if(keyName != "createdAt" && keyName != "updatedAt"){
                            if((i[keyName] ?? "").toString().trim().toLowerCase().includes(search.trim().toLocaleLowerCase())){
                                contains = true;
                                break;
                            }
                        }
                    }
                    if(contains){
                        newItems.push(i);
                    }
                });
                items = newItems;
            }

            response.success = true;
            response.data    = items;

        }catch(e: any){
            this.logger.error(e.message);
        }

        return response;
    }

    /**
     * Store new entity
     * @param request 
     * @param payload 
     * @param entity 
     * @returns 
     */
    async store(request: Request, payload: any, entity: string, restorePassword?: boolean): Promise<ResponseDto>{

        const response: ResponseDto = new ResponseDto();

        try{

            const result = this.entityManager.dataSource.entityMetadatas.find(m => m.tableName == entity);
            if(!result){
                throw new Error("Target not found");
            }

            if(payload && payload.password){
                payload.password = bcrypt.hashSync(payload.password, 10);
            }

            const repository = this.entityManager.dataSource.getRepository(result.target);
            if(payload.id && restorePassword){
                const filterEntity = 
                    await repository.findOneOrFail(
                        {
                            where: {
                                id: payload.id
                            }
                        }
                    );
                payload.password = filterEntity.password;
            }

            const insertOrUpdateResult = await repository.save(payload);

            response.success = true;
            response.data    = {"id": insertOrUpdateResult.id};

        }catch(e: any){
            this.logger.error(e.message);
        }

        return response;
    }

    /**
     * Remove an entity
     * @param request 
     * @param id 
     * @param entity 
     * @returns 
     */
    async remove(request: Request, id: number, entity: string): Promise<ResponseDto>{

        const response: ResponseDto = new ResponseDto();

        try{

            const result = this.entityManager.dataSource.entityMetadatas.find(m => m.tableName == entity);
            if(!result){
                throw new Error("Target not found");
            }
            const repository = this.entityManager.dataSource.getRepository(result.target);
            const entityToTrash = 
                await repository.findOneOrFail(
                    {
                        where: {
                            id: id
                        }
                    }
                );
            await repository.remove(entityToTrash);
            response.success = true;

        }catch(e: any){
            this.logger.error(e.message);
        }

        return response;    
    }
    
    /**
     * Preventive trash check
     * @param request 
     * @param entity 
     * @param id 
     */
    async preventiveTrash(request: Request, entity: string, id: number){

        const response: ResponseDto = new ResponseDto();
        response.data               = {"verbose": null};

        try{

            const result = this.entityManager.dataSource.entityMetadatas.find(m => m.tableName == entity);
            if(!result){
                throw new Error("Target not found");
            }

            switch(entity){
                case "category": {
                    const relationsCount = 
                        await this.entityManager.count(
                            CategoryEntity,
                            {
                                where: {
                                    category: {
                                        id: id
                                    }
                                }
                            }
                        );
                    if(relationsCount > 0){
                        response.data['verbose'] = `Attenzione, la categoria ha ${relationsCount} categorie figlie`;
                    }
                } break;
                case "tipology": {
                    const relationsCount = 
                        await this.entityManager.count(
                            AttributeEntity,
                            {
                                where: {
                                    tipology: {
                                        id: id
                                    }
                                }
                            }
                        );
                    if(relationsCount > 0){
                        response.data['verbose'] = `Attenzione, la tipologia ha legame con ${relationsCount} attributi`;
                    }
                } break;
                case "rate": {
                    const relationsCount = 
                        await this.entityManager.count(
                            ArticleEntity,
                            {
                                where: {
                                    rate: {
                                        id: id
                                    }
                                }
                            }
                        );
                    if(relationsCount > 0){
                        response.data['verbose'] = `Attenzione, l'aliquota ha legame con ${relationsCount} articoli`;
                    }
                } break;
                case "unit_measure": {
                    const relationsCount = 
                        await this.entityManager.count(
                            ArticleEntity,
                            {
                                where: {
                                    unitMeasure: {
                                        id: id
                                    }
                                }
                            }
                        );
                    if(relationsCount > 0){
                        response.data['verbose'] = `Attenzione, l'unità di misura ha legame con ${relationsCount} articoli`;
                    }
                } break;
            }

            response.success = true;

        }catch(e: any){
            this.logger.error(e.message);
        }

        return response;
    }

    /**
     * Post save event
     * @param request 
     * @param entity 
     * @param id 
     */
    async postSave(request: Request, entity: string, id: number): Promise<ResponseDto>{

        const response: ResponseDto = new ResponseDto();
        response.data               = {"verbose": null};

        try{

            const result = this.entityManager.dataSource.entityMetadatas.find(m => m.tableName == entity);
            if(!result){
                throw new Error("Target not found");
            }

            //switch(entity){
                // case "invoice_counter": {
                //     const invoiceCounter: InvoiceCounterEntity = 
                //         await this.entityManager.findOneOrFail(
                //             InvoiceCounterEntity,
                //             {
                //                 where: {
                //                     id: id
                //                 }
                //             }
                //         );
                //     if(invoiceCounter.active == 1){ // Disable all counters with id <> ${id}
                //         await this.entityManager.query(
                //             `UPDATE invoice_counter SET active = 0 WHERE id <> ?`,
                //             [ id ]
                //         );
                //     }
                // } break;
                // case "credit_note_counter": {
                //     const invoiceCounter: CreditNoteCounterEntity = 
                //         await this.entityManager.findOneOrFail(
                //             CreditNoteCounterEntity,
                //             {
                //                 where: {
                //                     id: id
                //                 }
                //             }
                //         );
                //     if(invoiceCounter.active == 1){ // Disable all counters with id <> ${id}
                //         await this.entityManager.query(
                //             `UPDATE credit_note_counter SET active = 0 WHERE id <> ?`,
                //             [ id ]
                //         );
                //     }
                // } break;
            //}

            response.success = true;

        }catch(e: any){
            this.logger.error(e.message);
        }

        return response;    
    }

    /**
     * Pre save event
     * @param request 
     * @param entity 
     * @param payload 
     * @returns 
     */
    async preSave(request: Request, entity: string, payload: any): Promise<ResponseDto>{

        const response: ResponseDto = new ResponseDto();
        response.data               = {"verbose": null};

        try{

            const result = this.entityManager.dataSource.entityMetadatas.find(m => m.tableName == entity);
            if(!result){
                throw new Error("Target not found");
            }

            switch(entity){
                case "category": {
                    if(payload.id && payload.category && payload.category.id){
                        if(payload.id == payload.category.id){
                            response['data']['verbose'] = "Attenzione, la categoria padre non può essere la stessa della categoria figlia";
                        }else{ // If parent category is children category
                            const category = 
                                await this.entityManager.findOneOrFail(
                                    CategoryEntity,
                                    {
                                        where: {
                                            id: payload.category.id
                                        },
                                        relations: {
                                            category: true
                                        }
                                    }
                                );
                            if(category && category.category && category.category.id == payload.id){
                                response['data']['verbose'] = "Attenzione, stai indicando una categoria figlia come padre";
                            }
                        }
                    }
                } break;
            }

            response.success = true;

        }catch(e: any){
            this.logger.error(e.message);
        }

        return response;    
    }

}