package services

import (
	"errors"
	"sort"
	"time"

	config "astrology-api/configs"
	"astrology-api/dto"
	models "astrology-api/models/usermodel"
	"astrology-api/notifications"
	repositories "astrology-api/repositories"
	"astrology-api/utils"
)

type walletService struct {
	repository repositories.WalletRepository
	phonepe    *PhonePeService
}

func NewWalletService(repository repositories.WalletRepository, phonepe *PhonePeService) WalletService {
	return &walletService{
		repository: repository,
		phonepe:    NewPhonePeService(),
	}
}

type WalletService interface {
	GetWalletTransactions(userID uint, req dto.WalletTransactionRequest) (*dto.WalletTransactionListResponse, error)

	GetWalletSummary(userID uint) (*dto.WalletSummaryResponse, error)

	GetWalletTransactionDetail(userID uint, req dto.WalletTransactionDetailRequest) (*dto.WalletTransactionDetailResponse, error)

	GetRechargeOffers() (*dto.RechargeOfferResponse, error)

	CreatePayment(userID uint, req dto.CreatePaymentRequest) (*dto.CreatePaymentResponse, error)

	VerifyPayment(userID uint, req dto.VerifyPaymentRequest) (*dto.VerifyPaymentResponse, error)
}

func (s *walletService) GetWalletSummary(
	userID uint,
) (*dto.WalletSummaryResponse, error) {

	return s.repository.GetWalletSummary(userID)
}

func (s *walletService) GetWalletTransactions(userID uint, req dto.WalletTransactionRequest) (*dto.WalletTransactionListResponse, error) {

	if req.Page <= 0 {
		req.Page = 1
	}

	if req.Limit <= 0 {
		req.Limit = 20
	}

	recharge, err := s.repository.GetRechargeTransactions(userID)
	if err != nil {
		return nil, err
	}

	chat, err := s.repository.GetChatTransactions(userID)
	if err != nil {
		return nil, err
	}

	call, err := s.repository.GetCallTransactions(userID)
	if err != nil {
		return nil, err
	}

	order, err := s.repository.GetOrderTransactions(userID)
	if err != nil {
		return nil, err
	}

	// Always initialize an empty slice
	records := make([]dto.WalletTransactionResponse, 0)

	records = append(records, recharge...)
	records = append(records, chat...)
	records = append(records, call...)
	records = append(records, order...)

	// parseTime=True scans DATETIME into a string as RFC3339, not "2006-01-02 15:04:05".
	parseCreatedAt := func(s string) time.Time {
		if t, err := time.Parse(time.RFC3339Nano, s); err == nil {
			return t
		}
		t, _ := time.ParseInLocation("2006-01-02 15:04:05", s, time.Local)
		return t
	}

	sort.SliceStable(records, func(i, j int) bool {
		return parseCreatedAt(records[i].CreatedAt).After(parseCreatedAt(records[j].CreatedAt))
	})

	total := len(records)

	start := (req.Page - 1) * req.Limit

	if start > total {
		start = total
	}

	end := start + req.Limit

	if end > total {
		end = total
	}

	// Ensure paginated slice is never nil
	recordList := make([]dto.WalletTransactionResponse, 0)

	if start < end {
		recordList = records[start:end]
	}

	response := &dto.WalletTransactionListResponse{
		RecordList:  recordList,
		TotalRecord: int64(total),
		CurrentPage: req.Page,
		Limit:       req.Limit,
		Status:      200,
	}

	return response, nil
}

// GetWalletTransactionDetail returns gorm.ErrRecordNotFound when the id does
// not belong to userID.
func (s *walletService) GetWalletTransactionDetail(userID uint, req dto.WalletTransactionDetailRequest) (*dto.WalletTransactionDetailResponse, error) {

	detail, err := s.repository.GetWalletTransactionDetail(userID, req.ID, req.TransactionType)
	if err != nil {
		return nil, err
	}

	return &dto.WalletTransactionDetailResponse{
		Status:  200,
		Message: "Transaction details fetched successfully",
		Data:    detail,
	}, nil
}

func (s *walletService) GetRechargeOffers() (*dto.RechargeOfferResponse, error) {

	offers, err := s.repository.GetRechargeOffers()
	if err != nil {
		return nil, err
	}

	response := dto.RechargeOfferResponse{
		Status:  200,
		Message: "Recharge offers fetched successfully",
	}

	for _, item := range offers {

		percent := 0

		if item.Amount > 0 {

			percent = int((item.Cashback / item.Amount) * 100)

		}

		response.Offers = append(response.Offers, dto.RechargeOffer{

			ID:            item.ID,
			Amount:        item.Amount,
			Cashback:      item.Cashback,
			ExtraPercent:  percent,
			ReceiveAmount: item.Amount + item.Cashback,
			MostPopular:   item.Amount == 100,
		})

	}

	return &response, nil
}
func (s *walletService) CreatePayment(
	userID uint,
	req dto.CreatePaymentRequest,
) (*dto.CreatePaymentResponse, error) {

	// ---------------- Validate ----------------

	if req.Amount <= 0 {
		return nil, errors.New("invalid recharge amount")
	}

	if req.Amount < 10 {
		return nil, errors.New("minimum recharge amount is ₹10")
	}

	// ---------------- Get User ----------------

	var user models.User
	if err := config.DB.First(&user, userID).Error; err != nil {
		return nil, err
	}

	merchantTxnID := s.phonepe.GenerateTransactionID(user.ID)
	orderID := s.phonepe.GenerateOrderID()

	tx := config.DB.Begin()

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

	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
		}
	}()

	phonePay := "PHONEPE"

	payment := models.Payment{
		UserID:           &userID,
		OrderID:          &orderID,
		PaymentReference: &merchantTxnID,
		PaymentMode:      &phonePay,
		Amount:           &req.Amount,
		PaymentStatus:    utils.PaymentPending,
	}

	if err := tx.Create(&payment).Error; err != nil {
		tx.Rollback()
		return nil, err
	}

	// ---------------- PhonePe ----------------

	phoneReq := s.phonepe.BuildRequest(
		user,
		req,
		merchantTxnID,
	)

	phoneResp, err := s.phonepe.CreateOrder(phoneReq)

	if err != nil {
		tx.Rollback()
		return nil, err
	}

	// ---------------- Failure ----------------

	if !phoneResp.Success {

		payment.PaymentStatus = utils.PaymentFailed

		if err := tx.Save(&payment).Error; err != nil {
			tx.Rollback()
			return nil, err
		}

		tx.Commit()

		return nil, errors.New(phoneResp.Message)
	}

	// ---------------- Success ----------------

	signature := phoneResp.Code

	payment.PaymentStatus = utils.PaymentPending
	payment.Signature = &signature

	if err := tx.Save(&payment).Error; err != nil {
		tx.Rollback()
		return nil, err
	}

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

	response := &dto.CreatePaymentResponse{

		Status: 200,

		Message: "Payment Initiated Successfully",

		OrderID: orderID,

		MerchantTransactionID: merchantTxnID,

		Platform: req.Platform,
	}

	if req.Platform == utils.PlatformWeb {

		response.PaymentURL =
			s.phonepe.PaymentURL(phoneResp)

	} else {

		response.IntentURL =
			s.phonepe.IntentURL(phoneResp)

	}

	return response, nil
}

func (s *walletService) VerifyPayment(userID uint, req dto.VerifyPaymentRequest) (*dto.VerifyPaymentResponse, error) {

	// -------------------------- Get Payment  --------------------------

	payment, err := s.repository.GetPaymentByTransactionID(
		req.MerchantTransactionID,
	)

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

	// -------------------------- Duplicate Check  --------------------------

	if payment.PaymentStatus == utils.PaymentSuccess {

		return nil, errors.New("payment already verified")

	}

	// -------------------------- PhonePe Status--------------------------

	statusResp, err := s.phonepe.GetPaymentStatus(req.MerchantTransactionID)

	if err != nil {

		return nil, err

	}

	if !statusResp.Success {

		return nil, errors.New(statusResp.Message)

	}

	if statusResp.Data.State != "COMPLETED" {

		return nil, errors.New("payment not completed")

	}

	// --------------------------
	// Start Transaction
	// --------------------------

	tx := config.DB.Begin()

	if tx.Error != nil {

		return nil, tx.Error

	}

	defer func() {

		if r := recover(); r != nil {

			tx.Rollback()

		}

	}()

	// --------------------------
	// Update Payment
	// --------------------------

	success := utils.PaymentSuccess

	payment.PaymentStatus = success

	signature := statusResp.Data.TransactionId

	payment.Signature = &signature

	if err := tx.Save(payment).Error; err != nil {

		tx.Rollback()

		return nil, err

	}

	// --------------------------
	// Locad  User Wallet
	// --------------------------
	wallet, err := s.repository.GetUserWallet(userID)

	if err != nil {

		tx.Rollback()

		return nil, err

	}

	// --------------------------
	// Cash Back
	// --------------------------

	cashback := 0.0

	if payment.CashbackAmount != nil {

		cashback = *payment.CashbackAmount

	}

	// --------------------------
	// Credited User Wallet
	// --------------------------
	rechargeAmount := 0.0

	if payment.Amount != nil {

		rechargeAmount = *payment.Amount

	}

	currentAmount := 0.0

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

	newAmount := currentAmount + rechargeAmount + cashback

	wallet.Amount = &newAmount

	if err := tx.Save(wallet).Error; err != nil {

		tx.Rollback()

		return nil, err

	}

	// --------------------------
	// Commit Transaction
	// --------------------------
	transactionType := "Recharge"

	isCredit := true

	transaction := models.WalletTransaction{

		UserID: userID,

		Amount: rechargeAmount,

		TransactionType: transactionType,

		OrderID: payment.OrderID,

		IsCredit: isCredit,
	}

	if err := tx.Create(&transaction).Error; err != nil {

		tx.Rollback()

		return nil, err

	}

	if err := tx.Commit().Error; err != nil {

		return nil, err

	}

	//--------------------------
	// Notify The Customer
	//--------------------------
	//
	// After the commit, never before: a customer must not be told about money
	// that a rolled-back transaction never credited. The cashback is included
	// in the credited figure because that is what landed in the wallet.

	notifications.Notify().WalletRecharged(userID, rechargeAmount+cashback, newAmount)

	return &dto.VerifyPaymentResponse{

		Status: true,

		Message: "Wallet credited successfully",

		PaymentStatus: success,

		TransactionID: statusResp.Data.TransactionId,

		OrderID: *payment.OrderID,

		Amount: rechargeAmount,

		WalletAmount: rechargeAmount,

		Cashback: cashback,
	}, nil

}
