const { Model, DataTypes, Op } = require('sequelize');
const sequelize = require('../config/db.js');
const Entity = require('./entity.js');
const { sanitizeSort, sanitizePagination } = require('./querySafety.js');

class Billable extends Model {

    static associate(models) {

        Billable.belongsTo(models.Entity, { 
            as: "billable_entity", 
            foreignKey: "ent_id"
        });

        Billable.belongsToMany(models.InvoiceTemplate, { 
            through: "invoice_billables"
        });

        Billable.belongsToMany(models.Tax, { 
            through: "billable_tax"
        }); 
    }

    async createBillable(data, creatorId) {
        try {
            const tableColumns = await this.getTableColumns();
            const payload = this.sanitizePayload({ ...data, creatorId }, tableColumns);

            const billable = await Billable.create(payload);
            console.log(billable.toJSON());
            return billable.toJSON();
        }
        catch (err) {
            console.log(err);
            return err;
        }
    }

    async getTableColumns() {
        const tableMeta = await sequelize.getQueryInterface().describeTable('billables');
        return new Set(Object.keys(tableMeta || {}));
    }

    getSelectableColumns(tableColumns) {
        const preferredColumns = [
            'id', 'name', 'type', 'description', 'taxes', 'shortcode',
            'currency', 'cost', 'status', 'creatorId', 'createdAt', 'updatedAt', 'ent_id', 'hsn'
        ];
        return preferredColumns.filter((column) => tableColumns.has(column));
    }

    sanitizePayload(payload, tableColumns) {
        return Object.fromEntries(
            Object.entries(payload || {}).filter(([key]) => tableColumns.has(key))
        );
    }

    async getBillableNames(id) {
        try {
            const tableColumns = await this.getTableColumns();
            const nameAttributes = ['id', 'name', 'currency', 'cost', 'taxes', 'hsn']
                .filter((column) => tableColumns.has(column));

            const billable = await Billable.findAll(
                {
                    include: [
                        {
                            model: Entity,
                            as: "billable_entity",
                            where: {
                                id: id
                            },
                            attributes: []
                        }
                    ],
                    attributes: nameAttributes
                }
            );
            console.log(billable);
            return billable;
        }
        catch (err) {
            console.log(err);
            return err;
        }
    }
    
    async readBillables(ordCol, ordDir, offsetVal, limitVal, id, searchTerm) {
        try {
            const tableColumns = await this.getTableColumns();
            const attributes = this.getSelectableColumns(tableColumns);
            const searchFields = [
                {
                    name: {
                        [Op.like]: `%${searchTerm}%`
                    }
                }
            ];

            if (tableColumns.has('shortcode')) {
                searchFields.push({
                    shortcode: {
                        [Op.like]: `%${searchTerm}%`
                    }
                });
            }

            const [safeOrderCol, safeOrderDir] = sanitizeSort(
                ordCol,
                ordDir,
                Array.from(tableColumns),
                'id'
            );
            const { safeOffset, safeLimit } = sanitizePagination(offsetVal, limitVal);

            const billables = await Billable.findAll({
                attributes,
                where: { 
                    ent_id: id,
                    ...(searchTerm
                        ? {
                            [Op.or]: searchFields
                        }
                    : {}),
                },
                include: [
                    {
                        model: Entity,
                        as: "billable_entity",
                        attributes: ["id", "name"]
                    }
                ],
                order: [[safeOrderCol, safeOrderDir]],
                offset: safeOffset,
                limit: safeLimit,
                raw: true,
                nest: false
            });
    
            console.log(billables);
            return billables;
        } catch (err) {
            console.log(err);
            return err;
        }
    }
    
    async readBillableRow(data) {
        try {
            const tableColumns = await this.getTableColumns();
            const attributes = this.getSelectableColumns(tableColumns);

            const billable = await Billable.findOne(
                {
                    attributes,
                    where: {
                        id: data.id
                    }
                }
            )
            console.log(billable.toJSON());
            return billable.toJSON();
        }
        catch (err) {
            console.log(err);
            return err;
        }
    } 

    async updateBillable(data) {
        try {
            const tableColumns = await this.getTableColumns();
            const payload = this.sanitizePayload({ ...data }, tableColumns);
            const targetId = payload.id;
            delete payload.id;

            const result = await Billable.update(payload, {where: {id: targetId}});
            console.log(`Billable with ${data.id} updated`, result[0]);
            return result[0];
        }
        catch (err) {
            console.log(err);
            return err;
        }
    }

    async deleteBillable(data) {
        try {
            const result = await Billable.destroy(
                {
                    where: {id: data.id}
                }
            );

            if (result) {
                console.log(`Billable ${data.id} deleted`);
                return `Billable ${data.id} deleted`;
            }
            else {
                console.log(`Billable ${data.id} not found`);
                return `Billable ${data.id} not found`;
            }
        }
        catch (err) {
            console.log(err);
            return err;
        }
    }

    async countBillable(id) {
        try {
            const count = await Billable.count({
                where: {
                    ent_id: id
                }
            });
            console.log(count);
            return count;
        }
        catch (err) {
            console.log(err);
            return err;
        }
    } 
}

Billable.init(
    {
        name: {
            type: DataTypes.STRING(50),
            allowNull: false
        },
        type: {
            type: DataTypes.STRING(50),
            allowNull: false
        },
        description: {
            type: DataTypes.TEXT,
            allowNull: true
        },
        hsn: {
            type: DataTypes.STRING(50),
            allowNull: true
        },
        taxes: {
            type: DataTypes.JSON,
            allowNull: true
        },
        shortcode: {
            type: DataTypes.STRING(25),
            allowNull: false
        },
        currency: {
            type: DataTypes.STRING(10),
            allowNull: true
        },
        cost: {
            type: DataTypes.STRING(50),
            allowNull: false
        },
        status: {
            type: DataTypes.STRING(25),
            allowNull: false
        },
        creatorId: {
            type: DataTypes.INTEGER,
            allowNull: false
        }
    },
    {
        sequelize,
        modelName: "Billable",
        tableName: "billables"
    }
)

module.exports = Billable;