package pkg

import (
	"context"
	"fmt"
	"github.com/jackc/pgtype"
	"github.com/jackc/pgx/v4"
)

type NewInvoiceProductArray []*invoiceProductForm

func (src NewInvoiceProductArray) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) {
	typeName := "new_invoice_product[]"
	dt, ok := ci.DataTypeForName(typeName)
	if !ok {
		return nil, fmt.Errorf("unable to find oid for type name %v", typeName)
	}
	var values [][]interface{}
	for _, form := range src {
		var productId interface{} = form.ProductId.Val
		if form.ProductId.Val == "" {
			productId = nil
		}
		values = append(values, []interface{}{
			productId,
			form.Name.Val,
			form.Description.Val,
			form.Price.Val,
			form.Quantity.Val,
			form.Discount.Float64() / 100.0,
			form.Tax.Selected,
		})
	}
	array := pgtype.NewValue(dt.Value).(pgtype.ValueTranscoder)
	if err := array.Set(values); err != nil {
		return nil, err
	}
	return array.EncodeBinary(ci, buf)
}

type EditedInvoiceProductArray []*invoiceProductForm

func (src EditedInvoiceProductArray) EncodeBinary(ci *pgtype.ConnInfo, buf []byte) ([]byte, error) {
	typeName := "edited_invoice_product[]"
	dt, ok := ci.DataTypeForName(typeName)
	if !ok {
		return nil, fmt.Errorf("unable to find oid for type name %v", typeName)
	}
	var values [][]interface{}
	for _, form := range src {
		var invoiceProductId interface{} = nil
		if form.InvoiceProductId.Val != "" {
			if id := form.InvoiceProductId.Integer(); id > 0 {
				invoiceProductId = id
			}
		}
		values = append(values, []interface{}{
			invoiceProductId,
			form.ProductId.Val,
			form.Name.Val,
			form.Description.Val,
			form.Price.Val,
			form.Quantity.Val,
			form.Discount.Float64() / 100.0,
			form.Tax.Selected,
		})
	}
	array := pgtype.NewValue(dt.Value).(pgtype.ValueTranscoder)
	if err := array.Set(values); err != nil {
		return nil, err
	}
	return array.EncodeBinary(ci, buf)
}

func registerPgTypes(ctx context.Context, conn *pgx.Conn) error {
	if _, err := conn.Exec(ctx, "set role to admin"); err != nil {
		return err
	}
	tagNameOID, err := registerPgType(ctx, conn, &pgtype.Text{}, "tag_name")
	if err != nil {
		return err
	}
	tagNameArray := pgtype.NewArrayType("tag_name[]", tagNameOID, func() pgtype.ValueTranscoder {
		return &pgtype.Text{}
	})
	if _, err := registerPgType(ctx, conn, tagNameArray, tagNameArray.TypeName()); err != nil {
		return err
	}
	discountRateOID, err := registerPgType(ctx, conn, &pgtype.Numeric{}, "discount_rate")
	if err != nil {
		return err
	}
	newInvoiceProduct, err := pgtype.NewCompositeType(
		"new_invoice_product",
		[]pgtype.CompositeTypeField{
			{"product_id", pgtype.Int4OID},
			{"name", pgtype.TextOID},
			{"description", pgtype.TextOID},
			{"price", pgtype.TextOID},
			{"quantity", pgtype.Int4OID},
			{"discount_rate", discountRateOID},
			{"tax", pgtype.Int4ArrayOID},
		},
		conn.ConnInfo(),
	)
	if err != nil {
		return err
	}
	newInvoiceProductOID, err := registerPgType(ctx, conn, newInvoiceProduct, newInvoiceProduct.TypeName())
	if err != nil {
		return err
	}
	newInvoiceProductArray := pgtype.NewArrayType("new_invoice_product[]", newInvoiceProductOID, func() pgtype.ValueTranscoder {
		value := newInvoiceProduct.NewTypeValue()
		return value.(pgtype.ValueTranscoder)
	})
	_, err = registerPgType(ctx, conn, newInvoiceProductArray, newInvoiceProductArray.TypeName())
	if err != nil {
		return err
	}

	editedInvoiceProduct, err := pgtype.NewCompositeType(
		"edited_invoice_product",
		[]pgtype.CompositeTypeField{
			{"invoice_product_id", pgtype.Int4OID},
			{"product_id", pgtype.Int4OID},
			{"name", pgtype.TextOID},
			{"description", pgtype.TextOID},
			{"price", pgtype.TextOID},
			{"quantity", pgtype.Int4OID},
			{"discount_rate", discountRateOID},
			{"tax", pgtype.Int4ArrayOID},
		},
		conn.ConnInfo(),
	)
	if err != nil {
		return err
	}
	editedInvoiceProductOID, err := registerPgType(ctx, conn, editedInvoiceProduct, editedInvoiceProduct.TypeName())
	if err != nil {
		return err
	}
	editedInvoiceProductArray := pgtype.NewArrayType("edited_invoice_product[]", editedInvoiceProductOID, func() pgtype.ValueTranscoder {
		value := editedInvoiceProduct.NewTypeValue()
		return value.(pgtype.ValueTranscoder)
	})
	_, err = registerPgType(ctx, conn, editedInvoiceProductArray, editedInvoiceProductArray.TypeName())
	if err != nil {
		return err
	}

	_, err = conn.Exec(ctx, "reset role")
	return err
}

func registerPgType(ctx context.Context, conn *pgx.Conn, value pgtype.Value, name string) (oid uint32, err error) {
	if err = conn.QueryRow(ctx, "select $1::regtype::oid", name).Scan(&oid); err != nil {
		return
	}
	conn.ConnInfo().RegisterDataType(pgtype.DataType{Value: value, Name: name, OID: oid})
	return
}
