import {  PcdtTransactionInterface } from "../interface/pcd-transaction.interface";
import { FindManyOptions, Repository, getConnection, QueryRunner, Connection } from "typeorm";
import { BadRequestException, NotAcceptableException, UnauthorizedException, InternalServerErrorException, MethodNotAllowedException, HttpException, Inject } from "@nestjs/common";
import { TransactionDetailDto } from "src/framework/custom/transaction-detail.dto";
//import { TENANT_CONNECTION } from "src/tenant/tenant.module";
// import { IintDto } from "../../dto/iint.dto";
// import { IintvrnoDto } from "../../dto/iintvrno.dto";
// import { IintService } from "../../tables/iint/iint.service";

export class PcdTransactionService<T> implements PcdtTransactionInterface {

    MAIN_KEY: string = "id";
    TXN_TYPE:string="";

    DETAIL_DATA_FORMAT: TransactionDetailDto[] = [];

    constructor(
        private connection: Connection,
        private repo: Repository<any>
    ) { }

    async preFindList(filter: FindManyOptions, ): Promise<boolean> {
        return true
    }

    async findList(filter: FindManyOptions, ): Promise<T[]> {
        if (!filter.where) filter.where = {};
        if (this.TXN_TYPE) filter.where[this.DETAIL_DATA_FORMAT[0].transactionTypeColumnName] = this.TXN_TYPE;
        // if (!filter.where[this.DETAIL_DATA_FORMAT[0].transactionTypeColumnName])
        //     return;

        if (!await this.preFindList(filter))
            throw new BadRequestException(`Unexpected Error Occured in findList`);
        let result: T[] = await this.repo.find(filter);

        return await this.postFindList(filter,  result);
    }

    async postFindList(filter: FindManyOptions, result: T[]): Promise<T[]> {
        return result;
    }

    async preFindOne(filter: FindManyOptions, ): Promise<boolean> {
        return true
    }

    async findOne(filter: FindManyOptions, id: string | number, ): Promise<T> {
        if (!filter.where) filter.where = {};
        if (this.TXN_TYPE) filter.where[this.DETAIL_DATA_FORMAT[0].transactionTypeColumnName] = this.TXN_TYPE;
        let orgFilter = Object.create(filter);

        filter.where[this.MAIN_KEY] = id;

        if (!await this.preFindOne(filter))
            throw new BadRequestException(`Unepected Error Occured in findOne`);
        //branch right check(); 
        let result: T = await this.repo.findOne(filter);

        if (!result)
            return result;

        result = await this.getDetailData(result, orgFilter, this.DETAIL_DATA_FORMAT);

        return await this.postFindOne(filter,  result);
    }

    async postFindOne(filter: FindManyOptions, result: T): Promise<T> {
        return result;
    }

    async preDeleteOne(filter: FindManyOptions, id: string | number, ): Promise<boolean> {
        return true;
    }

    async deleteOne(filter: any, id: string | number, ): Promise<any> {
        if (!filter || !filter.where) filter.where = {};
        if (this.TXN_TYPE) filter.where[this.DETAIL_DATA_FORMAT[0].transactionTypeColumnName] = this.TXN_TYPE;
        filter.where[this.MAIN_KEY] = id;

        const delFilter = await this.getDeleteFilter(filter);
        if (!delFilter[this.MAIN_KEY])
            throw new NotAcceptableException(`Please select a record`);
        if (!await this.preDeleteOne(filter, id,))
            throw new BadRequestException(`Unepected Error Occured in deleteOne`);
        const queryRunner = this.connection.createQueryRunner();
        await queryRunner.connect();
        try {
            await queryRunner.startTransaction();
            await this.deleteDetailDataTabels(id,  this.DETAIL_DATA_FORMAT, queryRunner);
            const result = await queryRunner.manager.delete(this.repo.metadata.name, id);
            await queryRunner.commitTransaction();
            return await this.postDeleteOne(filter,  result);
        } catch (err) {
            if (queryRunner.isTransactionActive)
                await queryRunner.rollbackTransaction();
            throw new InternalServerErrorException(`${err.message}`);
        }finally{
            await queryRunner.release();
        }
        // const result = await this.repo.delete(delFilter);

    }

    async postDeleteOne(filter: FindManyOptions, result: any): Promise<any> {
        return result;
    }

    async preCreateOne(body: T, ): Promise<boolean> {
        return true;
    }

    async beforeCreateOne(body: T, queryRunner: QueryRunner): Promise<boolean> {
        return true;
    }

    async createOne(body: T, ): Promise<T> {
        //console.log('--- incoming = ', body);
            
        if (this.TXN_TYPE) body[this.DETAIL_DATA_FORMAT[0].transactionTypeColumnName] = this.TXN_TYPE;
        if (!await this.preCreateOne(body))
            throw new BadRequestException(`Unexpected Error Occured in CreateOne`);
        for (let eagerCol of await this.getEagerColumns()) {
            delete body[eagerCol];
        }
        const queryRunner = this.connection.createQueryRunner();
        await queryRunner.connect();
        try {
            await queryRunner.startTransaction();
            
        if (this.TXN_TYPE) body[this.DETAIL_DATA_FORMAT[0].transactionTypeColumnName] = this.TXN_TYPE;
            // await this.checkAndSetVrNo(body, user, queryRunner);

            // for deleting deleted details from the angular
            for (let row of this.DETAIL_DATA_FORMAT) {
                body[row.detailName] = await this.deleteDetailData(body[row.detailName],  row, queryRunner);
            }
            

            body["createdBy"] = 'SYSTEM';
           body["modifiedBy"] = 'SYSTEM';
            if (!await this.beforeCreateOne(body,  queryRunner))
                throw new BadRequestException(`Unepected Error Occured in CreateOne`);

            const data = await queryRunner.manager.save(this.repo.metadata.name, body);
            await this.insertDetailData(body,  this.DETAIL_DATA_FORMAT, queryRunner, data);
            
            await this.afterCreateOne(body);
            await queryRunner.commitTransaction();


            const filter: FindManyOptions = await this.getFilterWithPrimaryKey(body);
            const result = await this.findOne(filter, data[this.MAIN_KEY]);

            return await this.postCreateOne(body,  result);

        } catch (err) {
            // since we have errors lets rollback changes we made
            console.log('--- err --', err);
            console.log('QueryFailedError', err.QueryFailedError);
            console.log('*******err name*****', err.name);
            console.log('-- message------', err.message);
            console.log('-- code------', err.code);

            console.log('--- err.status --', err.status);
            console.log("===is active===", queryRunner.isTransactionActive);
            if (queryRunner.isTransactionActive)
                await queryRunner.rollbackTransaction();
            console.log("heree===afterrollback");
            if (err.status && err.status == '401') throw new MethodNotAllowedException(`${err.message.message}`);
            if (!(err.name == "QueryFailedError")) if (queryRunner.isTransactionActive) await queryRunner.rollbackTransaction();
            //console.log("====after rollback====");
            if (err.name == "QueryFailedError") {
                if (err.precedingErrors && err.precedingErrors.length > 0 && err.precedingErrors[0].originalError)
                    throw new BadRequestException(`${err.precedingErrors[0].originalError}`);
                if (err.originalError)
                    throw new BadRequestException(`${err.originalError}`);
            }
            if (err.status && err.status == 500) throw new HttpException(err.message.error, err.message.statusCode);
            if (err.status) throw new HttpException(err.message, err.status);

            throw new InternalServerErrorException(`${err.message}`);
        }
        finally {
            console.log('--- finally --- ');
            await queryRunner.release();
        }

    }

    async afterCreateOne(body: T): Promise<void> {
    }

    async postCreateOne(body: T, result: T): Promise<T> {
        return result;
    }

    async insertDetailData(body: T, detailData: TransactionDetailDto[], queryRunner: QueryRunner, insertedData: T): Promise<void> {
        
        for (let row of detailData) {
            await this.insertDetailData2ndLevel(body[row.detailName],  row, queryRunner, insertedData, insertedData);
        }
    }

    async deleteDetailDataTabels(id: string | number,  detailDataFormat: TransactionDetailDto[], queryRunner: QueryRunner): Promise<any> {
        for (let row of detailDataFormat) {
            await this.deleteDetailTableData(id,  row, queryRunner);
        }
    }

    async deleteDetailTableData(id: string | number,  detailData: TransactionDetailDto, queryRunner: QueryRunner): Promise<any> {
        if (detailData.childDetail && detailData.childDetail.length > 0) {
            for (let child of detailData.childDetail) {
                await this.deleteDetailTableData(id,  child, queryRunner);
            }
        }
        if (id) {
            let filter = {};
            if(detailData.foreignKeyColumnName) {
                filter[detailData.foreignKeyColumnName] = id;
            
            }else{
                filter[detailData.foreignKeyAssignmentColumnName.columnName] = id;
            }
            await queryRunner.manager.delete(detailData.repo.metadata.name, filter);
        }
    }

    async deleteDetailData(body: T[], detailData: TransactionDetailDto, queryRunner: QueryRunner): Promise<T[]> {
        if (detailData.childDetail && detailData.childDetail.length > 0) {
            for (let child of detailData.childDetail) {
                if (body && body.length > 0) {
                    for (let row of body) {
                        if (row["crud"] == "D") {
                            for (let detData of row[child.detailName]) {
                                detData["crud"] = "D";
                            }
                        }
                        row[child.detailName] = await this.deleteDetailData(row[child.detailName],  child, queryRunner);
                    }
                }
            }
        }
        if (body) {
            await queryRunner.manager.remove(detailData.repo.metadata.name, body.filter((row: any) => row.crud == 'D'));
            body = body.filter((row: any) => row["crud"] != 'D');
            return body;
        } else {
            return [];
        }
    }

    async insertDetailData2ndLevel(body: T[], detailData: TransactionDetailDto, queryRunner: QueryRunner, headerData: T, detailHeaderData: T): Promise<void> {
         
        let deatilInsResult: any;
        let eagerCols: any = await this.getEagerColumns(detailData.repo);
        if (body) {
            for (let row of body) {
                for (let col of eagerCols) {
                    delete row[col];
                }
        if (this.TXN_TYPE) row[this.DETAIL_DATA_FORMAT[0].transactionTypeColumnName] = this.TXN_TYPE;
                 if(detailData.foreignKeyColumnName){
                    row[detailData.foreignKeyColumnName]=headerData[this.MAIN_KEY];
                }else{
                
                row[detailData.foreignKeyAssignmentColumnName.columnName] 
                = detailHeaderData[detailData.foreignKeyAssignmentColumnName.headerColumn];
                }
                row["createdBy"] = 'SYSTEM';
                row["modifiedBy"] = 'SYSTEM';
                if (detailData.addtionalSettingOfRowsInInsertion && detailData.addtionalSettingOfRowsInInsertion.length > 0) {
                    for (let addRow of detailData.addtionalSettingOfRowsInInsertion) {
                        row[addRow.columnName] = detailHeaderData[addRow.headerColumn];
                    }
                }

                deatilInsResult = await queryRunner.manager.save(detailData.repo.metadata.name, row);
                if (detailData.childDetail && detailData.childDetail.length > 0) {
                    for (let child of detailData.childDetail) {
                        if (body && body.length > 0) {
                                await this.insertDetailData2ndLevel(row[child.detailName],  child, queryRunner, headerData, deatilInsResult);
                        }
                    }
                }
            } 
        }

    }

    async getDetailData(result: any, filter: FindManyOptions, detailData: TransactionDetailDto[]): Promise<any> {
        for (let row of detailData) {
            result[row.detailName] = [];
            let filterDetail: FindManyOptions = Object.assign({}, filter);
            if (!filterDetail.where) filterDetail.where = {};
            for (let filterRow of row.filter) {
                filterDetail.where[filterRow.columnName] = filterRow.headerColumn ? result[filterRow.headerColumn] : filterRow.value;
            };
            if (row.orderByColumn) {
                if (!filterDetail.order) filterDetail["order"] = {};
                filterDetail.order[row.orderByColumn.columnName] = row.orderByColumn.sort;
            }
            result[row.detailName] = await row.repo.find(filterDetail);
            if (row.childDetail && row.childDetail.length > 0) {
                for (let detailRows of result[row.detailName])
                    detailRows = await this.getDetailData(detailRows, filter, row.childDetail);
            }
        };

        return result;
    }

    /*-- To get the filter using primary key  --*/
    async getFilterWithPrimaryKey(body: T): Promise<FindManyOptions> {
        const filter: FindManyOptions = { where: {} };
        const PRIMARY_KEYS = await this.getPrimaryKeys();
        await PRIMARY_KEYS.forEach((column) => {
            filter.where[column] = body[column];
        });
        return filter;
    }

    /*-- To get the filter for delete  --*/
    async getDeleteFilter(whereFilter: FindManyOptions): Promise<any> {
        const filter: FindManyOptions = {};
        const PRIMARY_KEYS = await this.getPrimaryKeys();
        await PRIMARY_KEYS.forEach((column) => {
            filter[column] = whereFilter.where[column];
        });
        return filter;
    }

    /*-- To get the Primary Keys of the entity  --*/
    async getPrimaryKeys(): Promise<string[]> {
        const PRIMARY_KEYS: string[] = [];
        await this.repo.metadata.primaryColumns.forEach((row) => {
            PRIMARY_KEYS.push(row.propertyName);
        });
        return PRIMARY_KEYS;
    }

    async getEagerColumns(repoDetail?: any): Promise<string[]> {
        /* 
        For getting Eager true columns having Many to One relations.
        We need to remove the eager columns before insertions or updations or it will not allow to save data (But save message will show, but data not updated) 
         */
        const COLUMNS: string[] = [];
        for (let columnProperty of (repoDetail ? repoDetail.metadata.eagerRelations : this.repo.metadata.eagerRelations)) { //AJ
            COLUMNS.push(columnProperty.propertyName);
        }
        return COLUMNS;
    }


}