// Copyright 2025 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package object

import (
	"errors"
	"fmt"

	"github.com/casdoor/casdoor/i18n"
)

func validateBalanceForTransaction(transaction *Transaction, amount float64, lang string) error {
	currency := transaction.Currency
	if currency == "" {
		currency = "USD"
	}

	if transaction.Tag == "Organization" {
		// Validate organization balance change
		return validateOrganizationBalance("admin", transaction.Owner, amount, currency, true, lang)
	} else if transaction.Tag == "User" {
		// Validate user balance change
		if transaction.User == "" {
			return errors.New(i18n.Translate(lang, "general:User is required for User category transaction"))
		}
		if err := validateUserBalance(transaction.Owner, transaction.User, amount, currency, lang); err != nil {
			return err
		}
		// Validate organization's user balance sum change
		return validateOrganizationBalance("admin", transaction.Owner, amount, currency, false, lang)
	}
	return nil
}

func validateOrganizationBalance(owner string, name string, balance float64, currency string, isOrgBalance bool, lang string) error {
	organization, err := getOrganization(owner, name)
	if err != nil {
		return err
	}
	if organization == nil {
		return fmt.Errorf(i18n.Translate(lang, "auth:the organization: %s is not found"), fmt.Sprintf("%s/%s", owner, name))
	}

	// Convert the balance amount from transaction currency to organization's balance currency
	balanceCurrency := organization.BalanceCurrency
	if balanceCurrency == "" {
		balanceCurrency = "USD"
	}
	convertedBalance := ConvertCurrency(balance, currency, balanceCurrency)

	var newBalance float64
	if isOrgBalance {
		newBalance = AddPrices(organization.OrgBalance, convertedBalance)
		// Check organization balance credit limit
		if newBalance < organization.BalanceCredit {
			return fmt.Errorf(i18n.Translate(lang, "general:Insufficient balance: new organization balance %v would be below credit limit %v"), newBalance, organization.BalanceCredit)
		}
	} else {
		// User balance is just a sum of all users' balances, no credit limit check here
		// Individual user credit limits are checked in validateUserBalance
		newBalance = AddPrices(organization.UserBalance, convertedBalance)
	}

	// In validation mode, we don't actually update the balance
	return nil
}

func validateUserBalance(owner string, name string, balance float64, currency string, lang string) error {
	user, err := getUser(owner, name)
	if err != nil {
		return err
	}
	if user == nil {
		return fmt.Errorf(i18n.Translate(lang, "general:The user: %s is not found"), fmt.Sprintf("%s/%s", owner, name))
	}

	// Convert the balance amount from transaction currency to user's balance currency
	balanceCurrency := user.BalanceCurrency
	var org *Organization
	if balanceCurrency == "" {
		// Get organization's balance currency as fallback
		org, err = getOrganization("admin", owner)
		if err == nil && org != nil && org.BalanceCurrency != "" {
			balanceCurrency = org.BalanceCurrency
		} else {
			balanceCurrency = "USD"
		}
	}
	convertedBalance := ConvertCurrency(balance, currency, balanceCurrency)

	// Calculate new balance
	newBalance := AddPrices(user.Balance, convertedBalance)

	// Check balance credit limit
	// User.BalanceCredit takes precedence over Organization.BalanceCredit
	var balanceCredit float64
	if user.BalanceCredit != 0 {
		balanceCredit = user.BalanceCredit
	} else {
		// Get organization's balance credit as fallback
		if org == nil {
			org, err = getOrganization("admin", owner)
			if err != nil {
				return err
			}
		}
		if org != nil {
			balanceCredit = org.BalanceCredit
		}
	}

	// Validate new balance against credit limit
	if newBalance < balanceCredit {
		return fmt.Errorf(i18n.Translate(lang, "general:Insufficient balance: new balance %v would be below credit limit %v"), newBalance, balanceCredit)
	}

	// In validation mode, we don't actually update the balance
	return nil
}
