
const db = require('../models');
const FileDBApi = require('./file');
const crypto = require('crypto');
const Utils = require('../utils');

const Sequelize = db.Sequelize;
const Op = Sequelize.Op;

module.exports = class ProjectsDBApi {

    static async create(data, options) {
        const currentUser = (options && options.currentUser) || { id: null };
        const transaction = (options && options.transaction) || undefined;

        const projects = await db.projects.create(
            {
                id: data.id || undefined,
                name: data.name || null,
                description: data.description || null,
                status: data.status || null,
                startDate: data.startDate || null,
                endDate: data.endDate || null,
                importHash: data.importHash || null,
                createdById: currentUser.id,
                updatedById: currentUser.id,
            },
            { transaction },
        );

        await projects.setTenant(data.tenant || null, { transaction });
        await projects.setOrganizations(data.organizations || null, { transaction });

        if (data.trials) {
            await projects.setTrials(data.trials, { transaction });
        }

        if (data.documentation) {
             // Expecting array of file objects or IDs
             // Since association is hasMany (Project hasMany Files), we can use setDocumentation
             // But File model is polymorphic-ish.
             // If we use standard setter, Sequelize handles foreign key update on File table.
             // But we need to make sure 'belongsTo' and 'belongsToColumn' are set if we rely on scope.
             // Usually, we update files manually or use a helper.
             // Let's try standard setter first.
             // If data.documentation is array of IDs:
             let fileIds = data.documentation;
             if (fileIds.length > 0 && typeof fileIds[0] === 'object') {
                 fileIds = fileIds.map(f => f.id);
             }
             
             // We need to update the files to point to this project
             await db.file.update(
                 {
                     belongsTo: 'projects',
                     belongsToColumn: 'documentation',
                     belongsToId: projects.id
                 },
                 {
                     where: {
                         id: { [Op.in]: fileIds }
                     },
                     transaction
                 }
             );
        }

        return projects;
    }

    static async update(id, data, options) {
        const currentUser = (options && options.currentUser) || { id: null };
        const transaction = (options && options.transaction) || undefined;

        const projects = await db.projects.findByPk(id, {}, { transaction });

        const updatePayload = {};
        if (data.name !== undefined) updatePayload.name = data.name;
        if (data.description !== undefined) updatePayload.description = data.description;
        if (data.status !== undefined) updatePayload.status = data.status;
        if (data.startDate !== undefined) updatePayload.startDate = data.startDate;
        if (data.endDate !== undefined) updatePayload.endDate = data.endDate;

        updatePayload.updatedById = currentUser.id;

        await projects.update(updatePayload, { transaction });

        if (data.tenant !== undefined) {
            await projects.setTenant(data.tenant, { transaction });
        }
        if (data.organizations !== undefined) {
            await projects.setOrganizations(data.organizations, { transaction });
        }
        if (data.trials !== undefined) {
            await projects.setTrials(data.trials, { transaction });
        }
        
        if (data.documentation !== undefined) {
             let fileIds = data.documentation;
             if (fileIds && fileIds.length > 0 && typeof fileIds[0] === 'object') {
                 fileIds = fileIds.map(f => f.id);
             }
             
             // Unlink old files? 
             // Ideally we should unset old ones.
             // For now, let's just update new ones.
             // If we want to support removal, we need to know which ones are removed.
             // Assuming data.documentation is the NEW complete list.
             
             // First, clear existing files for this project (optional, depends on logic)
             // or just overwrite.
             // If we want to keep files that are still in the list, and remove others:
             
             // 1. Set all files belonging to this project to null (orphaned)
             await db.file.update(
                 { belongsToId: null, belongsTo: null, belongsToColumn: null },
                 { where: { belongsTo: 'projects', belongsToId: projects.id }, transaction }
             );

             // 2. Set new files
             if (fileIds && fileIds.length > 0) {
                 await db.file.update(
                     {
                         belongsTo: 'projects',
                         belongsToColumn: 'documentation',
                         belongsToId: projects.id
                     },
                     {
                         where: {
                             id: { [Op.in]: fileIds }
                         },
                         transaction
                     }
                 );
             }
        }

        return projects;
    }

    static async deleteByIds(ids, options) {
        const currentUser = (options && options.currentUser) || { id: null };
        const transaction = (options && options.transaction) || undefined;

        const projects = await db.projects.findAll({
            where: { id: { [Op.in]: ids } },
            transaction,
        });

        for (const record of projects) {
            await record.destroy({ transaction });
        }
        return projects;
    }

    static async remove(id, options) {
        const currentUser = (options && options.currentUser) || { id: null };
        const transaction = (options && options.transaction) || undefined;

        const projects = await db.projects.findByPk(id, options);
        await projects.destroy({ transaction });
        return projects;
    }

    static async findBy(where, options) {
        const transaction = (options && options.transaction) || undefined;

        const projects = await db.projects.findOne(
            { where },
            { transaction },
        );

        if (!projects) {
            return projects;
        }

        const output = projects.get({ plain: true });

        output.tenant = await projects.getTenant({ transaction });
        output.organizations = await projects.getOrganizations({ transaction });
        output.trials = await projects.getTrials({ transaction });
        output.documentation = await db.file.findAll({
            where: {
                belongsTo: 'projects',
                belongsToId: projects.id,
                belongsToColumn: 'documentation'
            },
            transaction
        });

        return output;
    }

    static async findAll(filter, globalAccess, options) {
        const limit = filter.limit || 0;
        let offset = 0;
        let where = {};
        const currentPage = +filter.page;

        const user = (options && options.currentUser) || null;
        const userOrganizations = (user && user.organizations?.id) || null;

        if (userOrganizations) {
            if (options?.currentUser?.organizationsId) {
                where.organizationsId = options.currentUser.organizationsId;
            }
        }

        offset = currentPage * limit;

        const transaction = (options && options.transaction) || undefined;

        let include = [
            {
                model: db.tenants,
                as: 'tenant',
            },
            {
                model: db.organizations,
                as: 'organizations',
            },
            {
                model: db.trials,
                as: 'trials',
            }
        ];

        if (filter) {
            if (filter.id) {
                where = { ...where, ['id']: Utils.uuid(filter.id) };
            }
            if (filter.name) {
                where = { ...where, [Op.and]: Utils.ilike('projects', 'name', filter.name) };
            }
            if (filter.status) {
                where = { ...where, status: filter.status };
            }
        }

        if (globalAccess) {
            delete where.organizationsId;
        }

        const queryOptions = {
            where,
            include,
            distinct: true,
            order: filter.field && filter.sort ? [[filter.field, filter.sort]] : [['createdAt', 'desc']],
            transaction: options?.transaction,
        };

        if (!options?.countOnly) {
            queryOptions.limit = limit ? Number(limit) : undefined;
            queryOptions.offset = offset ? Number(offset) : undefined;
        }

        try {
            const { rows, count } = await db.projects.findAndCountAll(queryOptions);
            return {
                rows: options?.countOnly ? [] : rows,
                count: count
            };
        } catch (error) {
            console.error('Error executing query:', error);
            throw error;
        }
    }

    static async findAllAutocomplete(query, limit, offset, globalAccess, organizationId) {
        let where = {};
        if (!globalAccess && organizationId) {
            where.organizationsId = organizationId;
        }

        if (query) {
            where = {
                ...where,
                [Op.or]: [
                    { ['id']: Utils.uuid(query) },
                    Utils.ilike('projects', 'name', query),
                ],
            };
        }

        const records = await db.projects.findAll({
            attributes: ['id', 'name'],
            where,
            limit: limit ? Number(limit) : undefined,
            offset: offset ? Number(offset) : undefined,
            order: [['name', 'ASC']],
        });

        return records.map((record) => ({
            id: record.id,
            label: record.name,
        }));
    }
};
