import { Document, Model, Schema, model, FilterQuery, QuerySelector } from "mongoose";
import {
    CreateClientAccountMovementParams,
    FindClientAccountMovementsParams,
    IClientAccountMovement,
    IClientAccountMovementsAdapter,
    PaginateClientAccountMovementsParams,
    PaginateClientAccountMovementsResults,
} from "./ClientAccountMovements.Interface";

export interface IClientAccountMovementDocument
    extends Omit<IClientAccountMovement, "id">,
        Document {}

export class ClientAccountMovementsAdapterMongoose implements IClientAccountMovementsAdapter {
    schema: Schema;
    movementList: Model<IClientAccountMovementDocument>;

    constructor() {
        this.schema = new Schema({
            clientId: {
                type: Schema.Types.ObjectId,
                required: true,
                ref: "Client",
            },
            amount: { type: Number, required: true, default: 0 },
            description: { type: String, required: false, default: "" },
            type: { type: String, required: true },
            reference: { type: String, required: false, default: "" },
            status: { type: String, required: true },
            paymentMethod: { type: String, required: false },
            createdAt: { type: Date, required: true, default: Date.now },
            updatedAt: { type: Date, required: true, default: Date.now },
        });

        this.movementList = model<IClientAccountMovementDocument>(
            "ClientAccountMovement",
            this.schema
        );
    }

    public async create(data: CreateClientAccountMovementParams): Promise<IClientAccountMovement> {
        return await this.movementList.create(data);
    }

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

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

        const createdAtFilter: QuerySelector<Date> = {};

        if (filters.startDate) {
            createdAtFilter.$gte = filters.startDate;
        }

        if (filters.endDate) {
            createdAtFilter.$lte = filters.endDate;
        }

        if (Object.keys(createdAtFilter).length > 0) {
            searchCriteria.createdAt = createdAtFilter;
        }

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

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

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

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

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

        return searchCriteria;
    }

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

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

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

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

    public async find(
        filters: Omit<FindClientAccountMovementsParams, "sessionUser">
    ): Promise<IClientAccountMovement[]> {
        const searchCriteria = this.buildSearchCriteria(filters);
        return this.movementList.find(searchCriteria).exec();
    }

    public async findOne(
        filters: Omit<FindClientAccountMovementsParams, "sessionUser">
    ): Promise<IClientAccountMovementDocument | null> {
        return this.movementList.findOne(filters).exec();
    }
}
