module util.validation.transaction;

import handy_http_primitives;
import std.array;
import std.datetime;

import util.validation.common;
import transaction.dto;
import transaction.data;
import account.data;

// class AtLeastOneLinkedAccountRule : ValidationRule!AddTransactionPayload {
//     Optional!ValidationError validate(in AddTransactionPayload payload) {
//         if (!payload.creditedAccountId && !payload.debitedAccountId) {
//             return ValidationError("creditedAccountId", "At least one account must be linked.").toOptional();
//         }
//         return Optional!ValidationError.empty();
//     }
// }

void validateTransactionPayload(
    TransactionVendorRepository vendorRepo,
    TransactionCategoryRepository categoryRepo,
    AccountRepository accountRepo,
    in AddTransactionPayload payload
) {
    if (!payload.creditedAccountId && !payload.debitedAccountId) {
        throw new HttpStatusException(HttpStatus.BAD_REQUEST, "At least one account must be linked.");
    }
    if (
        payload.creditedAccountId &&
        payload.debitedAccountId &&
        payload.creditedAccountId.value == payload.debitedAccountId.value
    ) {
        throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Cannot link the same account as both credit and debit.");
    }
    if (payload.amount == 0) {
        throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Amount should be greater than 0.");
    }
    SysTime now = Clock.currTime(UTC());
    SysTime timestamp = validateTimestampFormat(payload.timestamp);
    if (timestamp > now) {
        throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Cannot create transaction in the future.");
    }
    if (payload.vendorId && !vendorRepo.existsById(payload.vendorId.value)) {
        throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Vendor doesn't exist.");
    }
    if (payload.categoryId && !categoryRepo.existsById(payload.categoryId.value)) {
        throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Category doesn't exist.");
    }
    if (payload.creditedAccountId && !accountRepo.existsById(payload.creditedAccountId.value)) {
        throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Credited account doesn't exist.");
    }
    if (payload.debitedAccountId && !accountRepo.existsById(payload.debitedAccountId.value)) {
        throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Debited account doesn't exist.");
    }
    validateTags(payload.tags);
    if (payload.lineItems.length > 0) {
        long lineItemsTotal = 0;
        foreach (lineItem; payload.lineItems) {
            if (lineItem.categoryId && !categoryRepo.existsById(lineItem.categoryId.value)) {
                throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Line item's category doesn't exist.");
            }
            if (lineItem.quantity == 0) {
                throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Line item's quantity should greater than zero.");
            }
            for (ulong i = 0; i < lineItem.quantity; i++) {
                lineItemsTotal += lineItem.valuePerItem;
            }
        }
        if (lineItemsTotal != payload.amount) {
            throw new HttpStatusException(
                HttpStatus.BAD_REQUEST,
                "Total of all line items doesn't equal the transaction's total."
            );
        }
    }
}
