import Discounts from "../Discounts/Discounts";
import {
    FindServicesParams,
    IServicesAdapter,
    IService,
    CreateServiceParams,
    UpdateServiceParams,
    PaginateServicesParams,
    PaginateServicesResults,
    SERVICE_PUBLISHED_STATUS,
    SysAdminPaginateServicesParams,
} from "./Service.Interface";
import mongoose, { Document, Model, Schema, model } from "mongoose";

export interface IServiceDocument extends Omit<IService, "id">, Document {}

export class ServicesAdapterMongoose implements IServicesAdapter {
    schema: Schema;
    serviceList: Model<IServiceDocument>;

    constructor() {
        this.schema = new Schema({
            companyId: { type: Schema.Types.ObjectId, required: true, ref: "Companie" },
            discountId: { type: Schema.Types.ObjectId, required: false, ref: "Discount" },
            categoryId: { type: Number, required: false },
            name: { type: String, required: true },
            description: { type: String, required: true },
            price: { type: Number, required: true, default: 0 },
            originalPrice: { type: Number, required: false, default: 0 },
            length: { type: Number, required: true, default: 0 },
            limit: { type: Number, required: true, default: 0 },
            image: { type: String, required: false },
            color: { type: String, required: false },
            fontColor: { type: String, required: false },
            fontShadowColor: { type: String, required: false },
            block: { type: String, required: false },
            city: { type: String, required: false },
            state: { type: String, required: false },
            country: { type: String, required: false },
            zipCode: { type: String, required: false },
            latitude: { type: Number, required: false, default: 0 },
            longitude: { type: Number, required: false, default: 0 },
            published: { type: String, required: false },
            banned: { type: Boolean, required: false, default: false },
        });

        this.serviceList = model<IServiceDocument>("Service", this.schema);
    }

    public async create(data: CreateServiceParams): Promise<IService> {
        return await this.serviceList.create(data);
    }

    public async update(data: UpdateServiceParams): Promise<void> {
        const updateService = await this.serviceList.findOne({ _id: data.id });

        if (!updateService) {
            throw new Error("Service not found");
        }

        if (data.categoryId) updateService.categoryId = data.categoryId;
        if (data.name) updateService.name = data.name;
        if (data.description) updateService.description = data.description;
        if (data.originalPrice) {
            updateService.originalPrice = data.originalPrice;
            //Si tiene un descuento aplicado tenemos que actualizar el precio
            //aplicando el descuento al precio original
            if (updateService.discountId) {
                const discount = await Discounts.discounts.discountsList.findOne({
                    _id: String(updateService.discountId),
                    companyId: data.companyId,
                });

                if (discount) {
                    updateService.price =
                        updateService.originalPrice - updateService.originalPrice * (discount.value / 100);
                }
            } else {
                updateService.price = updateService.originalPrice;
            }
        }
        if (data.length) updateService.length = data.length;
        if (data.limit) updateService.limit = data.limit;
        if (data.image) updateService.image = data.image;
        if (data.color) updateService.color = data.color;
        if (data.fontColor) updateService.fontColor = data.fontColor;
        if (data.fontShadowColor) updateService.fontShadowColor = data.fontShadowColor;
        if (data.block) updateService.block = data.block;
        if (data.city) updateService.city = data.city;
        if (data.state) updateService.state = data.state;
        if (data.country) updateService.country = data.country;
        if (data.zipCode) updateService.zipCode = data.zipCode;
        if (data.latitude) updateService.latitude = data.latitude;
        if (data.longitude) updateService.longitude = data.longitude;
        if (data.banned !== undefined) updateService.banned = data.banned;

        await updateService.save();
    }

    public async delete(id: string): Promise<void> {
        await this.serviceList.findByIdAndDelete(id).exec();
    }

    public async find(filters: FindServicesParams): Promise<IService[]> {
        return this.serviceList.find(filters).exec();
    }

    public async findOne(filters: FindServicesParams): Promise<IServiceDocument | null> {
        return this.serviceList.findOne(filters).exec();
    }

    public async paginate(filters: PaginateServicesParams): Promise<PaginateServicesResults> {
        const { page, limit, ...findFilters } = filters;
        const count = await this.serviceList.countDocuments(findFilters).exec();
        const skip = (page - 1) * limit;

        const results = await this.serviceList.find(findFilters).skip(skip).limit(limit).exec();

        return {
            data: results,
            page,
            pages: Math.ceil(count / limit),
        };
    }

    // Método para búsqueda con expresiones regulares y paginación
    public async paginateFiltered(data: PaginateServicesParams): Promise<PaginateServicesResults> {
        const skip = (data.page - 1) * data.limit;

        // Construye los criterios de búsqueda
        const searchCriteria: mongoose.FilterQuery<IService> = {};

        // Agrega filtro por query usando expresiones regulares para 'name' y 'description'
        if (data.query) {
            const regex = new RegExp(data.query, "i");
            searchCriteria.$or = [{ name: regex }, { description: regex }];
        }

        // Filtra por categoryId si está presente
        if (data.categoryId !== undefined) {
            searchCriteria.categoryId = data.categoryId;
        }

        if (data.companyId !== undefined) {
            searchCriteria.companyId = data.companyId;
        }

        if (data.serviceId !== undefined) {
            searchCriteria._id = data.serviceId;
        }

        // Filtra por rango de precios
        if (data.priceFrom !== undefined || data.priceTo !== undefined) {
            searchCriteria.price = {};
            if (data.priceFrom !== undefined) searchCriteria.price.$gte = data.priceFrom;
            if (data.priceTo !== undefined) searchCriteria.price.$lte = data.priceTo;
        }

        // Filtra por rango de longitud
        if (data.lengthFrom !== undefined || data.lengthTo !== undefined) {
            searchCriteria.length = {};
            if (data.lengthFrom !== undefined) searchCriteria.length.$gte = data.lengthFrom;
            if (data.lengthTo !== undefined) searchCriteria.length.$lte = data.lengthTo;
        }

        // Filtra por block si está presente
        if (data.block) {
            searchCriteria.block = data.block;
        }

        // Filtra por city si está presente
        if (data.city) {
            searchCriteria.city = data.city;
        }

        // Filtra por state si está presente
        if (data.state) {
            searchCriteria.state = data.state;
        }

        if (data.published !== undefined) {
            searchCriteria.published = data.published;
            if (data.published === SERVICE_PUBLISHED_STATUS.PUBLISHED) {
                searchCriteria.banned = { $ne: true };
            }
        }

        // Realiza la consulta con paginación
        const results = await this.serviceList
            .find(searchCriteria)
            .sort({ price: 1 }) // Ordena por precio ascendente
            .skip(skip)
            .limit(data.limit)
            .exec();

        // Cuenta el total de documentos que coinciden con los criterios de búsqueda
        const count = await this.serviceList.countDocuments(searchCriteria).exec();

        return {
            data: results,
            page: data.page,
            pages: Math.ceil(count / data.limit),
        };
    }

    public async sysAdminPaginate(data: SysAdminPaginateServicesParams): Promise<PaginateServicesResults> {
        const { page, limit, companyId } = data;
        const query: any = {};
        if (companyId) {
            query.companyId = companyId;
        }

        const count = await this.serviceList.countDocuments(query).exec();
        const skip = (page - 1) * limit;

        const results = await this.serviceList
            .find(query)
            .sort({ name: 1 })
            .skip(skip)
            .limit(limit)
            .exec();

        return {
            data: results,
            page: page,
            pages: Math.ceil(count / limit),
        };
    }
}
