package booking

import (
	"context"
	"strconv"
	"time"

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

type bookingCart struct {
	Draft              *paymentDraft
	Lines              []*cartLine
	Total              string
	DownPayment        string
	DownPaymentPercent int
	Enabled            bool
}

type cartLine struct {
	Concept  string
	Units    int
	Subtotal string
}

type paymentDraft struct {
	NumAdults          int
	NumTeenagers       int
	NumChildren        int
	NumDogs            int
	NumNights          int
	Nights             string
	Adults             string
	Teenagers          string
	Children           string
	Dogs               string
	TouristTax         string
	Total              string
	DownPaymentPercent int
	DownPayment        string
	Options            []*paymentOption
}

type paymentOption struct {
	ID       int
	Label    string
	Units    int
	Subtotal string
}

func draftPayment(ctx context.Context, conn *database.Conn, f *bookingForm, campsiteType string) (*paymentDraft, error) {
	if f.Dates == nil {
		return nil, nil
	}

	if f.Guests == nil {
		return nil, nil
	}

	arrivalDate, err := time.Parse(database.ISODateFormat, f.Dates.ArrivalDate.Val)
	if err != nil {
		return nil, nil
	}
	departureDate, err := time.Parse(database.ISODateFormat, f.Dates.DepartureDate.Val)
	if err != nil {
		return nil, nil
	}

	draft := &paymentDraft{}
	draft.NumAdults, err = strconv.Atoi(f.Guests.NumberAdults.Val)
	if err != nil {
		return nil, nil
	}
	draft.NumTeenagers, err = strconv.Atoi(f.Guests.NumberTeenagers.Val)
	if err != nil {
		return nil, nil
	}
	draft.NumChildren, err = strconv.Atoi(f.Guests.NumberChildren.Val)
	if err != nil {
		return nil, nil
	}
	if f.Guests.NumberDogs != nil {
		draft.NumDogs, err = strconv.Atoi(f.Guests.NumberDogs.Val)
		if err != nil {
			return nil, nil
		}
	}

	var zonePreferences string
	if f.Options != nil && f.Options.ZonePreferences != nil {
		zonePreferences = f.Options.ZonePreferences.Val
	}

	var acsiCard bool
	if f.Guests.ACSICard != nil {
		acsiCard = f.Guests.ACSICard.Checked
	}

	optionMap := make(map[int]*campsiteTypeOption)
	var typeOptions []*campsiteTypeOption
	if f.Options != nil {
		typeOptions = f.Options.Options
	}
	optionUnits := make([]*database.OptionUnits, 0, len(typeOptions))
	for _, option := range typeOptions {
		units, _ := strconv.Atoi(option.Input.Val)
		if units < 1 {
			continue
		}
		optionMap[option.ID] = option
		optionUnits = append(optionUnits, &database.OptionUnits{
			OptionID: option.ID,
			Units:    units,
		})
	}

	row := conn.QueryRow(ctx, `
		select payment.slug
		     , payment_id
		     , departure_date - arrival_date
		     , to_price(subtotal_nights, decimal_digits)
		     , to_price(subtotal_adults, decimal_digits)
		     , to_price(subtotal_teenagers, decimal_digits)
		     , to_price(subtotal_children, decimal_digits)
		     , to_price(subtotal_dogs, decimal_digits)
		     , to_price(subtotal_tourist_tax, decimal_digits)
		     , to_price(total, decimal_digits)
		     , to_price(payment.down_payment, decimal_digits)
		     , (payment.down_payment_percent * 100)::int
		from draft_payment($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) as payment
			join currency using (currency_code)
`,
		database.ZeroNullUUID(f.PaymentSlug.Val),
		arrivalDate,
		departureDate,
		campsiteType,
		draft.NumAdults,
		draft.NumTeenagers,
		draft.NumChildren,
		draft.NumDogs,
		zonePreferences,
		acsiCard,
		database.OptionUnitsArray(optionUnits),
	)
	var paymentID int
	if err = row.Scan(
		&f.PaymentSlug.Val,
		&paymentID,
		&draft.NumNights,
		&draft.Nights,
		&draft.Adults,
		&draft.Teenagers,
		&draft.Children,
		&draft.Dogs,
		&draft.TouristTax,
		&draft.Total,
		&draft.DownPayment,
		&draft.DownPaymentPercent,
	); err != nil {
		if database.ErrorIsNotFound(err) {
			return nil, nil
		}
		return nil, err
	}

	rows, err := conn.Query(ctx, `
		select campsite_type_option_id
		     , units
		     , to_price(subtotal, decimal_digits)
		from payment_option
		   join payment using (payment_id)
		   join currency using (currency_code)
		where payment_id = $1
		order by campsite_type_option_id
`, paymentID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	for rows.Next() {
		var optionID int
		var units int
		var subtotal string
		err = rows.Scan(&optionID, &units, &subtotal)
		if err != nil {
			return nil, err
		}
		option := optionMap[optionID]
		if option == nil {
			continue
		}
		draft.Options = append(draft.Options, &paymentOption{
			ID:       option.ID,
			Label:    option.Label,
			Units:    units,
			Subtotal: subtotal,
		})
	}
	if rows.Err() != nil {
		return nil, rows.Err()
	}

	return draft, nil
}

func newBookingCart(ctx context.Context, conn *database.Conn, f *bookingForm, campsiteType string) (*bookingCart, error) {
	cart := &bookingCart{
		Total: "0.0",
	}

	draft, err := draftPayment(ctx, conn, f, campsiteType)
	if err != nil {
		return nil, err
	}
	if draft == nil {
		return cart, nil
	}
	cart.Draft = draft
	cart.DownPaymentPercent = draft.DownPaymentPercent

	maybeAddLine := func(units int, subtotal string, concept string) {
		if units > 0 && subtotal != "" {
			cart.Lines = append(cart.Lines, &cartLine{
				Concept:  concept,
				Units:    units,
				Subtotal: subtotal,
			})
		}
	}
	maybeAddLine(draft.NumNights, draft.Nights, locale.PgettextNoop("Night", "cart"))
	maybeAddLine(draft.NumAdults, draft.Adults, locale.PgettextNoop("Adult", "cart"))
	maybeAddLine(draft.NumTeenagers, draft.Teenagers, locale.PgettextNoop("Teenager", "cart"))
	maybeAddLine(draft.NumChildren, draft.Children, locale.PgettextNoop("Child", "cart"))
	maybeAddLine(draft.NumDogs, draft.Dogs, locale.PgettextNoop("Dog", "cart"))

	for _, option := range draft.Options {
		maybeAddLine(option.Units, option.Subtotal, option.Label)
	}

	maybeAddLine(draft.NumAdults, draft.TouristTax, locale.PgettextNoop("Tourist tax", "cart"))

	if draft.Total != "0.0" {
		cart.Total = draft.Total
		cart.Enabled = f.Guests.Error == nil

		if draft.DownPayment != draft.Total {
			cart.DownPayment = draft.DownPayment
		}
	}

	return cart, nil
}