import { Document, Model, Schema, model, FilterQuery } from "mongoose";
import {
    CreatePaymentExtraParams,
    FindPaymentsParams,
    IPayment,
    IPaymentsAdapter,
    PaginatePaymentsParams,
    PaginatePaymentsResults,
} from "./Payments.Interface";
import {
    CLIENT_ACCOUNT_MOVEMENT_STATUS,
    CLIENT_ACCOUNT_PAYMENT_METHODS,
} from "../ClientAccountMovements/ClientAccountMovements.Interface";

export interface IPaymentDocument extends Omit<IPayment, "id" | "_id">, Document {
    _id: string;
}

export class PaymentsAdapterMongoose implements IPaymentsAdapter {
    schema: Schema;
    paymentsList: Model<IPaymentDocument>;

    constructor() {
        this.schema = new Schema({
            clientId: {
                type: Schema.Types.ObjectId,
                required: true,
                ref: "Client",
            },
            companyId: {
                type: Schema.Types.ObjectId,
                required: true,
                ref: "Company",
            },
            employeeId: {
                type: Schema.Types.ObjectId,
                required: true,
                ref: "Employee",
            },
            serviceId: {
                type: Schema.Types.ObjectId,
                required: true,
                ref: "Service",
            },
            appointmentId: {
                type: Schema.Types.ObjectId,
                required: true,
                ref: "Appointment",
            },
            amount: { type: Number, required: true, default: 0 },
            status: {
                type: String,
                required: true,
                default: CLIENT_ACCOUNT_MOVEMENT_STATUS.PENDING,
            },
            method: {
                type: String,
                required: true,
                default: CLIENT_ACCOUNT_PAYMENT_METHODS.CASH,
            },
            description: { type: String, required: false, default: "" },
            createdAt: { type: Date, required: true, default: Date.now },
            updatedAt: { type: Date, required: true, default: Date.now },
        });

        this.paymentsList = model<IPaymentDocument>("Payment", this.schema);
    }

    public async create(data: CreatePaymentExtraParams): Promise<IPayment> {
        return await this.paymentsList.create(data);
    }

    public async delete(id: string): Promise<void> {
        await this.paymentsList.deleteOne({ _id: id });
    }

    public async find(filters: Omit<FindPaymentsParams, "sessionUser">): Promise<IPayment[]> {
        const searchCriteria = this.buildSearchCriteria(filters);
        return await this.paymentsList.find(searchCriteria).exec();
    }

    public async findOne(
        filters: Omit<FindPaymentsParams, "sessionUser">
    ): Promise<IPaymentDocument | null> {
        return await this.paymentsList.findOne(filters).exec();
    }

    private buildSearchCriteria(
        filters: Omit<FindPaymentsParams, "sessionUser">
    ): FilterQuery<IPayment> {
        const searchCriteria: FilterQuery<IPayment> = {};

        if (filters.startDate) {
            searchCriteria.createdAt = {
                $gte: filters.startDate,
                $lte: filters.endDate,
            };
        }

        if (filters.status) {
            searchCriteria.status = filters.status;
        }

        if (filters.clientIds && filters.clientIds.length > 0) {
            searchCriteria.clientId = { $in: filters.clientIds };
        } else if (filters.clientId) {
            searchCriteria.clientId = filters.clientId;
        }

        if (filters.method) {
            searchCriteria.method = filters.method;
        }

        if (filters.companyId) {
            searchCriteria.companyId = filters.companyId;
        }

        if (filters.employeeId) {
            searchCriteria.employeeId = filters.employeeId;
        }

        if (filters.serviceId) {
            searchCriteria.serviceId = filters.serviceId;
        }

        if (filters.appointmentId) {
            searchCriteria.appointmentId = filters.appointmentId;
        }

        return searchCriteria;
    }

    public async paginate(filters: PaginatePaymentsParams): Promise<PaginatePaymentsResults> {
        const searchCriteria = this.buildSearchCriteria(filters);

        const count = await this.paymentsList.countDocuments(searchCriteria).exec();
        const skip = (filters.page - 1) * filters.limit;

        const results = await this.paymentsList
            .find(searchCriteria)
            .skip(skip)
            .limit(filters.limit)
            .sort({ createdAt: -1 })
            .exec();

        let pages = Math.ceil(count / filters.limit);

        if (pages < 1) {
            pages = 1;
        }

        return {
            data: results,
            page: filters.page,
            pages: pages,
        };
    }
}
