import fs from "fs";
import path from "path";
import { rm } from "fs/promises";
import { ServicesAdapterMongoose } from "./Service.Adapter.Mongoose";
import {
    CompanyServiceView,
    CreateServiceParams,
    UpdateServiceParams,
    FindServicesByCompanyParams,
    FindServicesByIdParams,
    IService,
    IServicesManager,
    PaginateServicesParams,
    PaginateServicesResults,
    SetServiceFileParams,
    PublicServicesResults,
    PublicServiceView,
    DeleteServicesByCompany,
    SetPublishedStatusParams,
    SERVICE_PUBLISHED_STATUS,
    FindServicesParams,
    ApplyDiscountParams,
    DeleteDiscountParams,
    SysAdminPaginateServicesParams,
    SysAdminSetServiceBannedParams,
} from "./Service.Interface";
import { EmployeeRoles } from "../Employees/Employees.Interface";
import UsersManager from "../Users/Users";
import CompaniesManager from "../Companies/Companies";
import EmployeesList from "../Employees/Employee";
import { NoPermissionMessage } from "../../helpers/NoPermissionMessage";
import { isNull } from "../../helpers/IsNull";
import getServiceImage from "../../helpers/getServiceImage";
import sharp from "sharp";
import MetricsList from "../../Models/Metrics/Metrics";
import { resizeAndCrop } from "../../helpers/resizeAndCrop";
import { TextObjectFilterResult } from "../TextObjectFilter.model";
import { validatePermissionsByCompany, validateSessionUser } from "../../helpers/check";
import Discounts from "../Discounts/Discounts";
import { DiscountType } from "../Discounts/Discounts.Interface";

class ServiceManager implements IServicesManager {
    services: ServicesAdapterMongoose;

    constructor() {
        this.services = new ServicesAdapterMongoose();
    }

    public async setPublishedStatus(data: SetPublishedStatusParams): Promise<void> {
        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

        if (!sessionUser) {
            throw new Error("No se ha encontrado el usuario o no tiene permisos para realizar esta acción");
        }

        const service = await this.services.findOne({ _id: data.serviceId });

        if (!service) {
            throw new Error("El servicio no existe");
        }

        const company = await CompaniesManager.companies.findOne({
            _id: service.companyId,
        });

        if (!company) {
            throw new Error("La compañia no existe");
        }

        if (!(await EmployeesList.checkPermission(company, data.sessionUser, EmployeeRoles.ADMIN))) {
            throw new Error(NoPermissionMessage());
        }

        service.published = data.published;
        await service.save();
    }

    public async deleteServicesByCompany(data: DeleteServicesByCompany): Promise<void> {
        const services = await this.services.find({
            companyId: data.companyId,
        });

        const rootDir =
            process.env.NODE_ENV !== "production"
                ? process.env.UPLOAD_SERVICE_FILES_ROOT_DEV
                : process.env.UPLOAD_SERVICE_FILES_ROOT_PRO;

        if (!rootDir) {
            throw new Error("No se ha encontrado la ruta de los archivos");
        }

        for (const service of services) {
            if (!service.id) {
                continue;
            }
            const serviceDir = path.join(__dirname, rootDir, service.id);

            try {
                await rm(serviceDir, { recursive: true, force: true });
            } catch (err) {
                console.error(`Error al eliminar el directorio ${serviceDir}:`, err);
            }
        }

        await this.services.serviceList.deleteMany({
            companyId: data.companyId,
        });
    }

    public async createService(data: CreateServiceParams): Promise<IService> {
        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

        if (!sessionUser) {
            throw new Error("No se ha encontrado el usuario o no tiene permisos para realizar esta acción");
        }

        const companyCheck = await CompaniesManager.companies.findOne({
            _id: data.companyId,
        });

        if (!companyCheck) {
            throw new Error("La compañia no existe");
        }

        if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) {
            throw new Error(NoPermissionMessage());
        }

        const checkService = await this.services.findOne({
            name: data.name,
            companyId: data.companyId,
        });

        if (checkService) {
            throw new Error("Ya existe un servicio con ese nombre");
        }

        const canAdd = await MetricsList.canAddService(companyCheck.ownerId);

        if (!canAdd) {
            throw new Error("Ha alcanzado el limite de servicios permitidos de acuerdo a su plan.");
        }

        const newService = await this.services.create({
            ...data,
            ...{
                categoryId: companyCheck.categoryId,
                block: companyCheck.block,
                city: companyCheck.city,
                state: companyCheck.state,
                country: companyCheck.country,
                zipCode: companyCheck.zipCode,
                latitude: companyCheck.latitude,
                longitude: companyCheck.longitude,
                originalPrice: data.originalPrice,
                price: data.originalPrice,
            },
        });

        await MetricsList.addService({
            userId: companyCheck.ownerId,
            quantity: 1,
        });

        return newService;
    }

    public async updateService(data: UpdateServiceParams): Promise<void> {
        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

        if (!sessionUser) {
            throw new Error("No se ha encontrado el usuario o no tiene permisos para realizar esta acción");
        }

        const companyCheck = await CompaniesManager.companies.findOne({
            _id: data.companyId,
        });

        if (!companyCheck) {
            throw new Error("La compañia no existe");
        }

        if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) {
            throw new Error(NoPermissionMessage());
        }

        await this.services.update(data);
    }

    public async applyDiscount(data: ApplyDiscountParams): Promise<void> {
        await validateSessionUser({ sessionUser: data.sessionUser });
        await validatePermissionsByCompany({
            companyId: data.companyId,
            sessionUser: data.sessionUser,
        });

        const service = await this.services.findOne({
            _id: data.serviceId,
            companyId: data.companyId,
        });

        if (!service) {
            throw new Error("El servicio no existe");
        }

        const discount = await Discounts.discounts.discountsList.findOne({
            _id: data.discountId,
            companyId: data.companyId,
        });

        if (!discount) {
            throw new Error("El descuento no existe");
        }

        if (discount.type == DiscountType.FIXED) {
            service.price = service.originalPrice - discount.value;
        } else if (discount.type == DiscountType.PERCENTAGE) {
            service.price = service.originalPrice - service.originalPrice * (discount.value / 100);
        }

        service.discountId = data.discountId;
        await service.save();
    }

    public async deleteDiscount(data: DeleteDiscountParams): Promise<void> {
        await validateSessionUser({ sessionUser: data.sessionUser });
        await validatePermissionsByCompany({
            companyId: data.companyId,
            sessionUser: data.sessionUser,
        });

        const service = await this.services.findOne({
            _id: data.serviceId,
            companyId: data.companyId,
        });

        if (!service) {
            throw new Error("El servicio no existe");
        }

        service.discountId = undefined;
        service.price = service.originalPrice;
        await service.save();
    }

    public async paginate(filters: PaginateServicesParams): Promise<PaginateServicesResults> {
        return await this.services.paginate(filters);
    }

    public async findByCompanyId(data: FindServicesByCompanyParams): Promise<CompanyServiceView[]> {
        const returnValue: CompanyServiceView[] = [];

        const companyServices = await this.services.find({
            companyId: data.companyId,
        });

        const defColor = "#" + String(process.env.DEFAULT_SERVICE_COLOR);
        const defFontColor = "#" + String(process.env.DEFAULT_SERVICE_FONT_COLOR);
        const defFontShadowColor = "#" + String(process.env.DEFAULT_SERVICE_FONT_SHADOW_COLOR);

        for (const service of companyServices) {
            let discountId = "";
            let discountName = "";
            let discountCode = "";
            let discountType = "";
            let discountValue = 0;

            if (service.discountId) {
                const discount = await Discounts.discounts.discountsList.findOne({
                    _id: String(service.discountId),
                    companyId: data.companyId,
                });

                if (discount) {
                    discountId = discount.id;
                    discountName = discount.name;
                    discountCode = discount.code;
                    discountType = discount.type;
                    discountValue = discount.value;
                }
            }

            returnValue.push({
                id: isNull<string>(service.id, ""),
                name: service.name,
                description: service.description,
                length: service.length,
                price: service.price,
                originalPrice: service.originalPrice,
                discountId: discountId,
                discountName: discountName,
                discountCode: discountCode,
                discountType: discountType,
                discountValue: discountValue,
                limit: service.limit,
                image: getServiceImage(isNull<string>(service.id, ""), isNull<string>(service.image, ""), ""),
                color: isNull<string>(service.color, defColor),
                fontColor: isNull<string>(service.fontColor, defFontColor),
                fontShadowColor: isNull<string>(service.fontShadowColor, defFontShadowColor),
                published: isNull<SERVICE_PUBLISHED_STATUS>(
                    service.published,
                    SERVICE_PUBLISHED_STATUS.PRIVATE
                ),
            });
        }

        return returnValue;
    }

    public async findById(data: FindServicesByIdParams): Promise<CompanyServiceView> {
        const service = await this.services.findOne({ _id: data.serviceId });

        if (!service) {
            throw new Error("El servicio no existe");
        }

        const defColor = "#" + String(process.env.DEFAULT_SERVICE_COLOR);
        const defFontColor = "#" + String(process.env.DEFAULT_SERVICE_FONT_COLOR);
        const defFontShadowColor = "#" + String(process.env.DEFAULT_SERVICE_FONT_SHADOW_COLOR);

        let discountId = "";
        let discountName = "";
        let discountCode = "";
        let discountType = "";
        let discountValue = 0;

        if (service.discountId) {
            const discount = await Discounts.discounts.discountsList.findOne({
                _id: String(service.discountId),
                companyId: String(service.companyId),
            });

            if (discount) {
                discountId = discount.id;
                discountName = discount.name;
                discountCode = discount.code;
                discountType = discount.type;
                discountValue = discount.value;
            }
        }

        return {
            id: isNull<string>(service.id, ""),
            name: service.name,
            description: service.description,
            length: service.length,
            price: service.price,
            originalPrice: service.originalPrice,
            discountId: discountId,
            discountName: discountName,
            discountCode: discountCode,
            discountType: discountType,
            discountValue: discountValue,
            limit: service.limit,
            image: getServiceImage(isNull<string>(service.id, ""), isNull<string>(service.image, ""), ""),
            color: isNull<string>(service.color, defColor),
            fontColor: isNull<string>(service.fontColor, defFontColor),
            fontShadowColor: isNull<string>(service.fontShadowColor, defFontShadowColor),
            published: isNull<SERVICE_PUBLISHED_STATUS>(service.published, SERVICE_PUBLISHED_STATUS.PRIVATE),
        };
    }

    public async setServiceImage(data: SetServiceFileParams): Promise<void> {
        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

        if (!sessionUser) {
            throw new Error("No se ha encontrado el usuario o no tiene permisos para realizar esta acción");
        }

        const companyCheck = await CompaniesManager.companies.findOne({
            _id: data.companyId,
        });

        if (!companyCheck) {
            throw new Error("La compañia no existe");
        }

        if (!(await EmployeesList.checkPermission(companyCheck, data.sessionUser, EmployeeRoles.ADMIN))) {
            throw new Error(NoPermissionMessage());
        }

        const serviceCheck = await this.services.findOne({
            _id: data.serviceId,
        });

        if (!serviceCheck) {
            throw new Error("El servicio no existe");
        }

        if (!data.file) {
            throw new Error("No se ha enviado el archivo");
        }

        const rootDir =
            process.env.NODE_ENV !== "production"
                ? process.env.UPLOAD_SERVICE_FILES_ROOT_DEV
                : process.env.UPLOAD_SERVICE_FILES_ROOT_PRO;

        const uploadPath = path.join(__dirname, rootDir + data.serviceId);

        if (!fs.existsSync(uploadPath)) {
            fs.mkdirSync(uploadPath, { recursive: true });
        }

        const fileExtension = data.file.originalname.split(".").pop();
        const newFileName = "image." + fileExtension;

        const filePath = path.join(uploadPath, newFileName);

        const bufferImage = await resizeAndCrop(sharp(data.file.buffer), 378, 378);

        bufferImage.webp().toFile(filePath);

        //await sharp(data.file.buffer).resize({ height: 150 }).webp().toFile(filePath);

        serviceCheck.image = newFileName;
        await serviceCheck.save();
    }

    public async publicServices(data: PaginateServicesParams): Promise<PublicServicesResults> {
        // Only fetch services that are marked as published
        data.published = SERVICE_PUBLISHED_STATUS.PUBLISHED;
        const services = await this.services.paginateFiltered(data);
        const formattedServices: PublicServiceView[] = [];

        for (const service of services.data) {
            const organization = await CompaniesManager.companies.findOne({
                _id: service.companyId,
            });

            // Only include services whose organization is also published
            if (!organization || organization.published !== "published" || organization.banned || service.banned) {
                continue;
            }

            const defColor = "#" + String(process.env.DEFAULT_HEADER_COLOR);
            const defFontColor = "#" + String(process.env.DEFAULT_HEADER_FONT_COLOR);
            const defFontShadowColor = "#" + String(process.env.DEFAULT_HEADER_FONT_SHADOW_COLOR);

            let discountId = "";
            let discountName = "";
            let discountCode = "";
            let discountType = "";
            let discountValue = 0;

            if (service.discountId) {
                const discount = await Discounts.discounts.discountsList.findOne({
                    _id: String(service.discountId),
                    companyId: String(service.companyId),
                });

                if (discount) {
                    discountId = discount.id;
                    discountName = discount.name;
                    discountCode = discount.code;
                    discountType = discount.type;
                    discountValue = discount.value;
                }
            }

            formattedServices.push({
                id: isNull<string>(service.id, ""),
                name: service.name,
                description: service.description,
                length: service.length,
                price: service.price,
                originalPrice: service.originalPrice,
                discountId: discountId,
                discountName: discountName,
                discountCode: discountCode,
                discountType: discountType,
                discountValue: discountValue,
                limit: service.limit,
                image: getServiceImage(isNull<string>(service.id, ""), isNull<string>(service.image, ""), ""),
                organizationId: isNull<string>(service.companyId, ""),
                organizationName: isNull<string>(organization.name, ""),
                categoryId: service.categoryId,
                color: isNull<string>(service.color, defColor),
                fontColor: isNull<string>(service.fontColor, defFontColor),
                fontShadowColor: isNull<string>(service.fontShadowColor, defFontShadowColor),
            });
        }

        const results: PublicServicesResults = {
            data: formattedServices,
            page: services.page,
            pages: services.pages,
        };

        return results;
    }

    public async textObjectFilter(data: FindServicesParams): Promise<TextObjectFilterResult[]> {
        const services = await this.services.find(data);
        const formattedResult: TextObjectFilterResult[] = [];

        for (const service of services) {
            formattedResult.push({
                id: isNull<string>(service.id, ""),
                name: service.name,
                image: "",
            });
        }

        return formattedResult;
    }

    public async sysAdminPaginate(data: SysAdminPaginateServicesParams): Promise<PaginateServicesResults> {
        return await this.services.sysAdminPaginate(data);
    }

    public async sysAdminSetBanned(data: SysAdminSetServiceBannedParams): Promise<void> {
        const serviceCheck = await this.services.findOne({ _id: data.id });
        if (!serviceCheck) {
            throw new Error("El servicio no existe");
        }
        await this.services.update({
            id: data.id,
            companyId: serviceCheck.companyId, // needed by UpdateServiceParams
            banned: data.banned,
            sessionUser: "" // sysadmin override
        });
    }
}

const ServiceList = new ServiceManager();

export default ServiceList;
