/*
 * SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
 * SPDX-License-Identifier: AGPL-3.0-only
 */

package redsys

import (
	"context"
	"fmt"
	"github.com/jackc/pgtype"
	"golang.org/x/text/language"
	"time"

	"dev.tandem.ws/tandem/camper/pkg/auth"
	"dev.tandem.ws/tandem/camper/pkg/database"
)

type TransactionType int
type ResponseCode int

const (
	TransactionTypeCharge         TransactionType = 0
	TransactionTypeRefund         TransactionType = 3
	TransactionTypePreauth        TransactionType = 1
	TransactionTypePreauthConfirm TransactionType = 2
	TransactionTypePreauthVoid    TransactionType = 9
	TransactionTypeSplitAuth      TransactionType = 7
	TransactionTypeSplitConfirm   TransactionType = 8

	ResponsePaymentAuthorized   ResponseCode = 99
	ResponseRefundAuthorized    ResponseCode = 900
	ResponseCancelAuthorized    ResponseCode = 400
	ResponseCardExpired         ResponseCode = 101
	ResponseCardException       ResponseCode = 102
	ResponseTooManyPINAttempts  ResponseCode = 106
	ResponseCardNotEffective    ResponseCode = 125
	ResponseIncorrectSecureCode ResponseCode = 129
	ResponseDenied              ResponseCode = 172
)

type Request struct {
	TransactionType  TransactionType
	Amount           string
	OrderNumber      string
	Product          string
	CardHolder       string
	SuccessURL       string
	FailureURL       string
	NotificationURL  string
	ConsumerLanguage language.Tag
}

func (req Request) Sign(ctx context.Context, conn *database.Conn, company *auth.Company) (*SignedRequest, error) {
	row := conn.QueryRow(ctx, "select redsys_sign_request($1, $2)", company.ID, req)
	signed := &SignedRequest{}
	if err := row.Scan(&signed); err != nil {
		return nil, err
	}
	return signed, nil
}

func (req Request) EncodeText(ci *pgtype.ConnInfo, dst []byte) ([]byte, error) {
	typeName := database.RedsysRequestTypeName
	dt, ok := ci.DataTypeForName(typeName)
	if !ok {
		return nil, fmt.Errorf("unable to find oid for type name %v", typeName)
	}
	values := []interface{}{
		req.TransactionType,
		req.Amount,
		req.OrderNumber,
		req.Product,
		req.CardHolder,
		req.SuccessURL,
		req.FailureURL,
		req.NotificationURL,
		req.ConsumerLanguage,
	}
	ct := pgtype.NewValue(dt.Value).(*pgtype.CompositeType)
	if err := ct.Set(values); err != nil {
		return nil, err
	}
	return ct.EncodeText(ci, dst)
}

type SignedRequest struct {
	MerchantParameters string
	Signature          string
	SignatureVersion   string
}

func (dst *SignedRequest) DecodeText(ci *pgtype.ConnInfo, src []byte) error {
	typeName := database.RedsysSignedRequestTypeName
	dt, ok := ci.DataTypeForName(typeName)
	if !ok {
		return fmt.Errorf("unable to find oid for type name %v", typeName)
	}
	ct := pgtype.NewValue(dt.Value).(*pgtype.CompositeType)
	if err := ct.DecodeText(ci, src); err != nil {
		return err
	}
	return ct.AssignTo(dst)
}

type SignedResponse struct {
	MerchantParameters string
	Signature          string
	SignatureVersion   string
}

func (signed SignedResponse) Decode(ctx context.Context, conn *database.Conn, company *auth.Company) (*Response, error) {
	row := conn.QueryRow(ctx, "select redsys_decode_response($1, $2, $3, $4)", company.ID, signed.MerchantParameters, signed.Signature, signed.SignatureVersion)
	response := &Response{}
	if err := row.Scan(&response); err != nil {
		return nil, err
	}
	return response, nil
}

type Response struct {
	MerchantCode      string
	TerminalNumber    int
	ResponseCode      ResponseCode
	DateTime          time.Time
	SecurePayment     bool
	TransactionType   TransactionType
	Amount            string
	CurrencyCode      string
	OrderNumber       string
	AuthorizationCode string
	ErrorCode         string
}

func (response *Response) Process(ctx context.Context, conn *database.Conn, paymentSlug string) (string, error) {
	return conn.GetText(ctx, "select process_payment_response($1, $2)", paymentSlug, response)
}

func (response *Response) EncodeBinary(ci *pgtype.ConnInfo, dst []byte) ([]byte, error) {
	typeName := database.RedsysResponseTypeName
	dt, ok := ci.DataTypeForName(typeName)
	if !ok {
		return nil, fmt.Errorf("unable to find oid for type name %v", typeName)
	}
	values := []interface{}{
		response.MerchantCode,
		response.TerminalNumber,
		response.ResponseCode,
		response.DateTime,
		response.SecurePayment,
		response.TransactionType,
		response.Amount,
		response.CurrencyCode,
		response.OrderNumber,
		response.AuthorizationCode,
		response.ErrorCode,
	}
	ct := pgtype.NewValue(dt.Value).(*pgtype.CompositeType)
	if err := ct.Set(values); err != nil {
		return nil, err
	}
	return ct.EncodeBinary(ci, dst)
}