import { Injectable, Logger } from "@nestjs/common";
import { ArticleBarcodeDto, ArticleCreateDto, ArticleImageDto, ArticlePriceDto } from "../../dto/article.dto";
import ResponseDto from "../../dto/response.dto";
import { EntityManager } from "typeorm";
import ArticleEntity from "../../entity/article.entity";
import RateEntity from "../../entity/rate.entity";
import UnitMeasureEntity from "../../entity/unit_measure.entity";
import ArticleCategoryEntity from "../../entity/article_category.entity";
import CategoryEntity from "../../entity/category.entity";
import ArticleStoreEntity from "../../entity/article_store.entity";
import StoreEntity from "../../entity/store.entity";
import ArticlePriceEntity from "../../entity/article_price.entity";
import PriceEntity from "../../entity/price.entity";
import ArticleBarcodeEntity from "../../entity/article_barcode.entity";
import ArticleImageEntity from "../../entity/article_images.entity";
import MediaEntity from "../../entity/media.entity";
import HelperProvider from "../helper/helper.provider";
import StorageEntity from "../../entity/storage.entity";

@Injectable()
export default class ArticleProvider {
    
    private logger: Logger = new Logger(ArticleProvider.name);
    
    constructor(private entityManager: EntityManager, private readonly helperProvider: HelperProvider){}

    /**
     * Create article
     * @param request 
     * @param payload 
     * @returns 
     */
    async create(request: Request, payload: ArticleCreateDto[]): Promise<ResponseDto>{

        const response: ResponseDto = new ResponseDto();

        try{

            await this.entityManager.transaction(async (txn) => {

                for await (const item of payload){

                    const article               = new ArticleEntity();
                    article.attribute           = item.attribute;
                    article.availableOnPos      = item.availableOnPos;
                    article.code                = item.code;
                    article.id                  = item.id;
                    article.longDescription     = item.longDescription;
                    article.rate                = new RateEntity(item.idRate);
                    article.shortDescription    = item.shortDescription;
                    article.sku                 = item.sku;
                    article.subtitle            = item.subtitle;
                    article.title               = item.title;
                    article.discount            = item.discount;
                    article.unitMeasure         = new UnitMeasureEntity(item.idUnitMeasure);

                    await txn.save(article);

                    // Get rate value
                    const rate =
                        await this.entityManager.findOneOrFail(
                            RateEntity,
                            {
                                where: {
                                    id: item.idRate
                                }
                            }
                        );

                    // Trash joins
                    await txn.query(`DELETE FROM article_barcode WHERE id_article = ?`, [article.id]);
                    await txn.query(`DELETE FROM article_category WHERE id_article = ?`, [article.id]);
                    await txn.query(`DELETE FROM article_price WHERE id_article = ?`, [article.id]);
                    await txn.query(`DELETE FROM article_store WHERE id_article = ?`, [article.id]);
                    await txn.query(`DELETE FROM article_image WHERE id_article = ?`, [article.id]);

                    // Rewrite joins
                    for await (const idCategory of (item.idCategories ?? [])){

                        const articleCategory: ArticleCategoryEntity = new ArticleCategoryEntity();
                        articleCategory.category                     = new CategoryEntity(idCategory);
                        articleCategory.article                      = new ArticleEntity(article.id);

                        await txn.save(articleCategory);
                    }

                    for await (const idStore of (item.idStores ?? [])){

                        const articleStore: ArticleStoreEntity = new ArticleStoreEntity();
                        articleStore.store                     = new StoreEntity(idStore);
                        articleStore.article                   = new ArticleEntity(article.id);

                        await txn.save(articleStore);
                    }

                    for await (const price of (item.prices ?? [])){

                        const articlePrice: ArticlePriceEntity = new ArticlePriceEntity();
                        articlePrice.priceEntity               = new PriceEntity(price.idPriceList);
                        articlePrice.article                   = new ArticleEntity(article.id);
                        articlePrice.price                     = price.price;
                        articlePrice.taxable                   = price.taxable;
                        articlePrice.tax                       = (price.taxable! * rate.value!) / 100;
                        articlePrice.rate                      = price.idRate ? new RateEntity(price.idRate) : null;
                        
                        await txn.save(articlePrice);
                    }

                    for await (const barcode of (item.barcodes ?? [])){

                        const articleBarcode: ArticleBarcodeEntity  = new ArticleBarcodeEntity();
                        articleBarcode.barcode                      = barcode.barcode;
                        articleBarcode.article                      = new ArticleEntity(article.id);
                        articleBarcode.ratio                        = barcode.ratio;

                        await txn.save(articleBarcode);
                    }

                    for await (const image of (item.images ?? [])){

                        const articleImage: ArticleImageEntity  = new ArticleImageEntity();
                        articleImage.article                    = new ArticleEntity(article.id);
                        articleImage.coverPos                   = image.coverPos;
                        articleImage.link                       = image.link;
                        articleImage.media                      = image.idMedia ? new MediaEntity(image.idMedia) : null;

                        await txn.save(articleImage);
                    }

                }

            });
            
            response.success = true;

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

        return response;
    }

    /**
     * Get article by id
     * @param request 
     * @param id 
     */
    async getArticleById(request: Request, id: number): Promise<ResponseDto>{

        const response: ResponseDto = new ResponseDto();

        try{

            const article = 
                await this.entityManager.findOneOrFail(
                    ArticleEntity,
                    {
                        where: {
                            id: id
                        },
                        relations: {
                            unitMeasure: true,
                            rate: true
                        }
                    }
                );

            // Get relations
                
            const articlePrices = 
                await this.entityManager.find(
                    ArticlePriceEntity,
                    {
                        where: {
                            article: {
                                id: id
                            }
                        },
                        relations: {
                            priceEntity: true,
                            rate: true
                        }
                    }
                );

            const articleBarcodes = 
                await this.entityManager.find(
                    ArticleBarcodeEntity,
                    {
                        where: {
                            article: {
                                id: id
                            }
                        }
                    }
                );
            
            const articleCategories = 
                await this.entityManager.find(
                    ArticleCategoryEntity,
                    {
                        where: {
                            article: {
                                id: id
                            }
                        },
                        relations: {
                            category: true
                        }
                    }
                );

            const articleStores = 
                await this.entityManager.find(
                    ArticleStoreEntity,
                    {
                        where: {
                            article: {
                                id: id
                            }
                        },
                        relations: {
                            store: true
                        }
                    }
                );
            
            const articleImages = 
                await this.entityManager.find(
                    ArticleImageEntity,
                    {
                        where: {
                            article: {
                                id: id
                            }
                        },
                        relations: {
                            media: true
                        }
                    }
                );
            
            const idsCategories: number[] = [];
            articleCategories.forEach(ac => {
                idsCategories.push(ac.category!.id!);
            });

            const idsStores: number[] = [];
            articleStores.forEach(ast => {
                idsStores.push(ast.store!.id!);
            });

            const articlePricesDto: ArticlePriceDto[] = [];
            articlePrices.forEach(ap => {

                const item: ArticlePriceDto = new ArticlePriceDto();
                item.taxable                = ap.taxable;
                item.price                  = ap.price;
                item.idPriceList            = ap.priceEntity!.id!;
                item.idRate                 = ap.rate ? ap.rate.id : null;

                articlePricesDto.push(item);
            });

            const articleBarcodesDto: ArticleBarcodeDto [] = [];
            articleBarcodes.forEach(ab => {

                const articleBarcodeDto: ArticleBarcodeDto  = new ArticleBarcodeDto();
                articleBarcodeDto.barcode                   = ab.barcode;
                articleBarcodeDto.ratio                     = ab.ratio;

                articleBarcodesDto.push(articleBarcodeDto);
            }); 

            const articleImagesDto: ArticleImageDto[] = [];
            articleImages.forEach(aim => {

                const articleImage: ArticleImageDto = new ArticleImageDto();
                articleImage.coverPos               = aim.coverPos;
                articleImage.idMedia                = aim.media ? aim.media.id : null;
                articleImage.link                   = aim.link;

                articleImagesDto.push(articleImage);
            });

            const articleDto: ArticleCreateDto  = new ArticleCreateDto();
            articleDto.attribute                = article.attribute;
            articleDto.availableOnPos           = article.availableOnPos;
            articleDto.code                     = article.code;
            articleDto.id                       = article.id;
            articleDto.longDescription          = article.longDescription;
            articleDto.shortDescription         = article.shortDescription;
            articleDto.sku                      = article.sku;
            articleDto.subtitle                 = article.subtitle;
            articleDto.title                    = article.title;
            articleDto.discount                 = article.discount;
            articleDto.idUnitMeasure            = article.unitMeasure?.id;
            articleDto.idRate                   = article.rate?.id;
            articleDto.barcodes                 = articleBarcodes;
            articleDto.idCategories             = idsCategories;
            articleDto.idStores                 = idsStores;
            articleDto.prices                   = articlePricesDto;
            articleDto.barcodes                 = articleBarcodesDto;
            articleDto.images                   = articleImagesDto;

            response.success = true;
            response.data    = articleDto;

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

        return response;
    }

    /**
     * Search article
     * @param request 
     * @param searchBy 
     * @param text 
     * @returns 
     */
    async searchArticle(request: Request, searchBy: 'code' | 'title' | 'barcode', text: string): Promise<ResponseDto>{

        const response: ResponseDto = new ResponseDto();

        try{

            let result: any;
            let usedIndex = false;
            

            switch(searchBy){
                case 'title':
                case 'code': {
                    result = [];
                    if(searchBy == "code"){
                        result = 
                            await this.entityManager.query(
                                `SELECT id, code, title FROM article WHERE code = ?`,
                                [ text ]
                            );
                    }
                    if(result.length == 0){
                        result = 
                            await this.entityManager.query(
                                `SELECT id, code, title FROM article WHERE title LIKE ?`,
                                [ `%${text}%` ]
                            );
                    }else{
                        usedIndex = true;
                    }
                } break;    
                case 'barcode': {

                    // Try to get before exact to use index
                    result = 
                        await this.entityManager.query(
                            `SELECT a.id, a.code, a.title FROM article_barcode ab INNER JOIN article a ON a.id = ab.id_article WHERE ab.barcode = ?`,
                            [ text ]
                        );                    

                    if(result.length == 0){
                        result = 
                            await this.entityManager.query(
                                `SELECT a.id, a.code, a.title FROM article_barcode ab INNER JOIN article a ON a.id = ab.id_article WHERE ab.barcode LIKE ?`,
                                [ `%${text}%` ]
                            );
                    }else{
                        usedIndex = true;
                    }

                } break;
                default: {
                    throw new Error("invalid_search_by");
                }
            }

            response.success = true; // Maybe not found nothig, but query not errored!
            response.data    = {"articles": result, "usedIndex": usedIndex};

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

        return response;
    }

    /**
     * Get article stock
     * @param request 
     * @param stockFor 
     * @param idArticle 
     */
    async getArticleStock(request: Request, stockFor: 'article' | 'store' | 'storage', idArticle: number): Promise<ResponseDto>{

        const response: ResponseDto = new ResponseDto();

        try{
            switch(stockFor){
                
                // _______ Global stock _______
                case "article": {
                    const stock         = await this.helperProvider.getStock(idArticle);
                    response.data       = { "stock": stock };
                    response.success    = true;
                } break;

                // _______ Store stock _______
                case "store": {
                    
                    const storeStocks: any[]    = [];
                    const stores: StoreEntity[] = await this.entityManager.find(StoreEntity);

                    for await (const store of stores){
                        const stock = await this.helperProvider.getStock(idArticle, store.id);
                        storeStocks.push(
                            { "idStore": store.id, "title": store.title, "stock": stock }
                        );
                    }
                    response.data    = storeStocks;
                    response.success = true;

                } break;

                // _______ Storage stock _______
                case "storage": {

                    const storageStocks: any[]      = [];
                    const storages: StorageEntity[] = 
                        await this.entityManager.find(
                            StorageEntity,
                            {
                                relations: {
                                    store: true
                                }
                            }
                        );
                    for await (const storage of storages){
                        if(storage.store){
                            const stock = await this.helperProvider.getStock(idArticle, storage.store.id, storage.id);
                            storageStocks.push(
                                { "idStorage": storage.id, "title": storage.title, "stock": stock }
                            );  
                        }
                    }

                    response.data    = storageStocks;
                    response.success = true;

                } break;

            }
        }catch(e: any){

        }

        return response;
    }

}