package services

import (
	"errors"
	"fmt"

	configs "astrology-api/configs"
	usermodel "astrology-api/models/usermodel"
	"astrology-api/repositories"

	"gorm.io/gorm"
)

//////////////////////////////////////////////////////////////
// Shared Astrology Plumbing
//////////////////////////////////////////////////////////////

// Price flag names in systemflag. A missing row or "0" means the service is
// free, so a service can ship before its price is decided.
const (
	FlagPriceKundaliMatching = "KundaliMatchingPrice"
	FlagPriceHoroscope       = "HoroscopePrice"
	FlagPricePanchang        = "PanchangPrice"
	FlagPriceNumerology      = "NumerologyPrice"
)

// Wallet transaction types, following the existing "KundliView" value.
const (
	TxnKundaliMatching = "KundaliMatching"
	TxnHoroscope       = "HoroscopeView"
	TxnPanchang        = "PanchangView"
	TxnNumerology      = "NumerologyView"
)

type astrologyBase struct {
	db   *gorm.DB
	repo repositories.AstrologyRepository

	// neverCharge is set for the astrologer routes: astrologers use these
	// services free, and on that side the token's users.id is the
	// astrologer's own earnings wallet, which must never be debited.
	neverCharge bool
}

func newAstrologyBase() astrologyBase {

	return astrologyBase{
		db:   configs.DB,
		repo: repositories.NewAstrologyRepository(configs.DB),
	}
}

//////////////////////////////////////////////////////////////
// Vedic Client
//////////////////////////////////////////////////////////////

func (b *astrologyBase) vedic() (*VedicClient, error) {

	apiKey, err := b.repo.GetVedicAPIKey()

	if err != nil {
		return nil, err
	}

	return NewVedicClient(apiKey), nil
}

//////////////////////////////////////////////////////////////
// Wallet
//////////////////////////////////////////////////////////////

// chargeWallet deducts amount inside the caller's transaction and records the
// ledger entry. The price is always resolved server-side, never taken from the
// request body.
func (b *astrologyBase) chargeWallet(
	tx *gorm.DB,
	userID uint,
	amount float64,
	txnType string,
) error {

	// While the admin catalogue is readable it prices every astrology service
	// (services/astrology_pricing.go, charged by the usage middleware), so the
	// systemflag price is not charged on top of it.
	if amount <= 0 || b.neverCharge || CatalogueGovernsPricing() {
		return nil
	}

	wallet, err := b.repo.GetUserWallet(userID)

	if err != nil {
		return errors.New("wallet not found")
	}

	balance := float64(0)

	if wallet.Amount != nil {
		balance = *wallet.Amount
	}

	if balance < amount {
		return fmt.Errorf("insufficient wallet balance, %.2f required", amount)
	}

	remaining := balance - amount
	wallet.Amount = &remaining

	if err := b.repo.UpdateWallet(tx, wallet); err != nil {
		return err
	}

	transaction := &usermodel.WalletTransaction{
		UserID:          userID,
		Amount:          amount,
		IsCredit:        false,
		TransactionType: txnType,
		CreatedBy:       &userID,
		ModifiedBy:      &userID,
	}

	return b.repo.CreateWalletTransaction(tx, transaction)
}

// chargeForRead charges a proxied read in its own transaction. Free services
// (price 0) do no wallet work at all.
func (b *astrologyBase) chargeForRead(
	userID uint,
	priceFlag string,
	txnType string,
) (float64, error) {

	if b.neverCharge || CatalogueGovernsPricing() {
		return 0, nil
	}

	price, err := b.repo.GetServicePrice(priceFlag)

	if err != nil {
		return 0, err
	}

	if price <= 0 {
		return 0, nil
	}

	tx := b.db.Begin()

	if tx.Error != nil {
		return 0, tx.Error
	}

	if err := b.chargeWallet(tx, userID, price, txnType); err != nil {
		tx.Rollback()
		return 0, err
	}

	if err := tx.Commit().Error; err != nil {
		return 0, err
	}

	return price, nil
}

// freeAstrologyBase is astrologyBase for the astrologer routes: every charge
// is skipped, so a price set in systemflag applies to customers only.
func freeAstrologyBase() astrologyBase {

	base := newAstrologyBase()
	base.neverCharge = true

	return base
}
