package services

import (
	"astrology-api/dto"
	models "astrology-api/models/usermodel"
	"bytes"
	"errors"
	"fmt"
	"net/http"
	"time"

	config "astrology-api/configs"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
)

type PhonePePaymentRequest struct {
	MerchantID            string                   `json:"merchantId"`
	MerchantTransactionID string                   `json:"merchantTransactionId"`
	MerchantUserID        string                   `json:"merchantUserId"`
	Amount                int64                    `json:"amount"`
	RedirectURL           string                   `json:"redirectUrl"`
	RedirectMode          string                   `json:"redirectMode"`
	CallbackURL           string                   `json:"callbackUrl"`
	MobileNumber          string                   `json:"mobileNumber"`
	PaymentInstrument     PhonePePaymentInstrument `json:"paymentInstrument"`
}

type PhonePePaymentInstrument struct {
	Type      string `json:"type"`
	TargetApp string `json:"targetApp,omitempty"`
}

type PhonePeResponse struct {
	Success bool   `json:"success"`
	Code    string `json:"code"`
	Message string `json:"message"`

	Data struct {
		MerchantTransactionID string `json:"merchantTransactionId"`

		InstrumentResponse struct {
			RedirectInfo struct {
				URL string `json:"url"`
			} `json:"redirectInfo"`

			IntentURL string `json:"intentUrl"`
		} `json:"instrumentResponse"`
	} `json:"data"`
}

type PhonePeService struct{}

func NewPhonePeService() *PhonePeService {

	return &PhonePeService{}

}

func (p *PhonePeService) GenerateTransactionID(userID uint) string {

	return fmt.Sprintf(
		"TXN%d%d",
		userID,
		time.Now().UnixMilli(),
	)

}

func (p *PhonePeService) GenerateOrderID() string {

	b := make([]byte, 16)
	if _, err := rand.Read(b); err != nil {
		return fmt.Sprintf("ORDER-%d", time.Now().UnixNano())
	}
	return hex.EncodeToString(b)

}

func (p *PhonePeService) EncodePayload(req PhonePePaymentRequest) (string, error) {

	body, err := json.Marshal(req)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(body), nil

}

func (p *PhonePeService) GenerateXVerify(encoded string) string {
	hash := sha256.Sum256([]byte(encoded + "/pg/v1/pay" + config.PhonePe.SaltKey))

	return hex.EncodeToString(hash[:]) + "###" + config.PhonePe.SaltIndex

}

func (p *PhonePeService) BuildRequest(user models.User, req dto.CreatePaymentRequest, transactionID string) PhonePePaymentRequest {

	payment := PhonePePaymentRequest{MerchantID: config.PhonePe.MerchantID, MerchantTransactionID: transactionID, MerchantUserID: config.PhonePe.MerchantUser,
		Amount: int64(req.Amount * 100), RedirectURL: config.PhonePe.RedirectURL, RedirectMode: "POST", CallbackURL: config.PhonePe.CallbackURL, MobileNumber: user.ContactNo}

	if req.Platform == "web" {
		payment.PaymentInstrument.Type = "PAY_PAGE"

	} else {

		payment.PaymentInstrument.Type = "UPI_INTENT"

		payment.PaymentInstrument.TargetApp = "PHONEPE"

	}
	return payment

}

func (p *PhonePeService) CreateOrder(request PhonePePaymentRequest) (*PhonePeResponse, error) {

	encoded, err := p.EncodePayload(request)
	if err != nil {
		return nil, err
	}
	xVerify := p.GenerateXVerify(encoded)
	body := map[string]string{
		"request": encoded,
	}

	jsonBody, _ := json.Marshal(body)
	httpReq, _ := http.NewRequest(
		http.MethodPost,
		config.PhonePe.BaseURL+"/pg/v1/pay",
		bytes.NewBuffer(jsonBody),
	)

	httpReq.Header.Set("Content-Type", "application/json")

	httpReq.Header.Set("X-VERIFY", xVerify)

	client := http.Client{

		Timeout: 30 * time.Second,
	}

	resp, err := client.Do(httpReq)

	if err != nil {

		return nil, err

	}

	defer resp.Body.Close()

	var phoneResp PhonePeResponse

	err = json.NewDecoder(resp.Body).Decode(&phoneResp)

	if err != nil {

		return nil, err

	}

	return &phoneResp, nil

}

func (p *PhonePeService) PaymentURL(response *PhonePeResponse) string {

	if response == nil {

		return ""

	}

	return response.Data.InstrumentResponse.RedirectInfo.URL

}

func (p *PhonePeService) IntentURL(response *PhonePeResponse) string {

	if response == nil {

		return ""

	}

	return response.Data.InstrumentResponse.IntentURL

}

func (p *PhonePeService) HandleWebPayment(response *PhonePeResponse) (*dto.CreatePaymentResponse, error) {

	if response == nil {
		return nil, errors.New("empty response from PhonePe")
	}

	if !response.Success {
		return nil, errors.New(response.Message)
	}

	redirectURL := response.Data.InstrumentResponse.RedirectInfo.URL

	if redirectURL == "" {
		return nil, errors.New("payment url not received from PhonePe")
	}

	return &dto.CreatePaymentResponse{
		Status:     200,
		Message:    "Payment Initiated Successfully",
		PaymentURL: redirectURL,
	}, nil
}

func (p *PhonePeService) GenerateStatusXVerify(transactionID string) string {

	hash := sha256.Sum256([]byte("/pg/v1/status/" + config.PhonePe.MerchantID + "/" + transactionID + config.PhonePe.SaltKey))

	return hex.EncodeToString(hash[:]) + "###" + config.PhonePe.SaltIndex

}

func (p *PhonePeService) GetPaymentStatus(transactionID string) (*PhonePeStatusResponse, error) {

	url := fmt.Sprintf("%s/pg/v1/status/%s/%s", config.PhonePe.BaseURL, config.PhonePe.MerchantID, transactionID)
	req, err := http.NewRequest(http.MethodGet, url, nil)

	if err != nil {

		return nil, err

	}

	req.Header.Set(

		"Content-Type",

		"application/json",
	)

	req.Header.Set(

		"accept",

		"application/json",
	)

	req.Header.Set(

		"X-VERIFY",

		p.GenerateStatusXVerify(transactionID),
	)

	req.Header.Set(

		"X-MERCHANT-ID",

		config.PhonePe.MerchantID,
	)

	client := http.Client{

		Timeout: 30 * time.Second,
	}

	resp, err := client.Do(req)

	if err != nil {

		return nil, err

	}

	defer resp.Body.Close()

	var phoneResp PhonePeStatusResponse

	err = json.NewDecoder(resp.Body).Decode(&phoneResp)

	if err != nil {

		return nil, err

	}

	return &phoneResp, nil

}
