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

package booking

import (
	"context"
	"golang.org/x/text/language"
	"net/http"
	"strings"
	"time"

	"dev.tandem.ws/tandem/camper/pkg/auth"
	"dev.tandem.ws/tandem/camper/pkg/database"
	"dev.tandem.ws/tandem/camper/pkg/form"
	httplib "dev.tandem.ws/tandem/camper/pkg/http"
	"dev.tandem.ws/tandem/camper/pkg/locale"
	"dev.tandem.ws/tandem/camper/pkg/template"
)

type AdminHandler struct {
}

func NewAdminHandler() *AdminHandler {
	return &AdminHandler{}
}

func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var head string
		head, r.URL.Path = httplib.ShiftPath(r.URL.Path)

		switch head {
		case "":
			switch r.Method {
			case http.MethodGet:
				serveBookingIndex(w, r, user, company, conn)
			default:
				httplib.MethodNotAllowed(w, r, http.MethodGet)
			}
		case "payment":
			head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
			switch head {
			case "":
				switch r.Method {
				case http.MethodGet:
					f := newPaymentForm(user.Locale)
					if err := f.FillFromDatabase(r.Context(), company, conn); err != nil {
						if !database.ErrorIsNotFound(err) {
							panic(err)
						}
					}
					f.MustRender(w, r, user, company)
				case http.MethodPut:
					updatePaymentSettings(w, r, user, company, conn)
				default:
					httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
				}
			default:
				http.NotFound(w, r)
			}
		default:
			http.NotFound(w, r)
		}
	})
}

func serveBookingIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
	bookings, err := collectBookingEntries(r.Context(), conn, user.Locale.Language)
	if err != nil {
		panic(err)
	}
	page := bookingIndex(bookings)
	page.MustRender(w, r, user, company)
}

func collectBookingEntries(ctx context.Context, conn *database.Conn, lang language.Tag) ([]*bookingEntry, error) {
	rows, err := conn.Query(ctx, `
		select left(slug::text, 10)
		     , '/admin/booking/' || slug
		     , arrival_date
		     , departure_date
		     , holder_name
		, booking.booking_status
		, coalesce(i18n.name, status.name)
		from booking
		    join booking_status as status using (booking_status)
			left join booking_status_i18n as i18n on status.booking_status = i18n.booking_status and i18n.lang_tag = $1
		order by arrival_date desc
	`, lang)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var entries []*bookingEntry
	for rows.Next() {
		entry := &bookingEntry{}
		if err = rows.Scan(&entry.Reference, &entry.URL, &entry.ArrivalDate, &entry.DepartureDate, &entry.HolderName, &entry.Status, &entry.StatusLabel); err != nil {
			return nil, err
		}
		entries = append(entries, entry)
	}

	return entries, nil
}

type bookingEntry struct {
	Reference     string
	URL           string
	ArrivalDate   time.Time
	DepartureDate time.Time
	HolderName    string
	Status        string
	StatusLabel   string
}

type bookingIndex []*bookingEntry

func (page bookingIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
	switch r.URL.Query().Get("format") {
	case "ods":
		columns := []string{
			"Reference",
			"Arrival Date",
			"Departure Date",
			"Holder Name",
			"Status",
		}
		ods, err := writeTableOds(page, columns, user.Locale, func(sb *strings.Builder, entry *bookingEntry) error {
			if err := writeCellString(sb, entry.Reference); err != nil {
				return err
			}
			writeCellDate(sb, entry.ArrivalDate)
			writeCellDate(sb, entry.DepartureDate)
			if err := writeCellString(sb, entry.HolderName); err != nil {
				return err
			}
			if err := writeCellString(sb, entry.StatusLabel); err != nil {
				return err
			}
			return nil
		})
		if err != nil {
			panic(err)
		}
		mustWriteOdsResponse(w, ods, user.Locale.Pgettext("bookings.ods", "filename"))
	default:
		template.MustRenderAdmin(w, r, user, company, "booking/index.gohtml", page)
	}
}

type paymentForm struct {
	MerchantCode   *form.Input
	TerminalNumber *form.Input
	Environment    *form.Select
	Integration    *form.Select
	EncryptKey     *form.Input
}

func newPaymentForm(l *locale.Locale) *paymentForm {
	return &paymentForm{

		MerchantCode: &form.Input{
			Name: "merchant_code",
		},
		TerminalNumber: &form.Input{
			Name: "terminal_number",
		},
		Environment: &form.Select{
			Name: "environment",
			Options: []*form.Option{
				{
					Value: "test",
					Label: l.Pgettext("Test", "redsys environment"),
				},
				{
					Value: "live",
					Label: l.Pgettext("Live", "redsys environment"),
				},
			},
		},
		Integration: &form.Select{
			Name: "integration",
			Options: []*form.Option{
				{
					Value: "insite",
					Label: l.Pgettext("InSite", "redsys integration"),
				},
				{
					Value: "redirect",
					Label: l.Pgettext("Redirect", "redsys integration"),
				},
			},
		},
		EncryptKey: &form.Input{
			Name: "encrypt_key",
		},
	}
}

func (f *paymentForm) FillFromDatabase(ctx context.Context, company *auth.Company, conn *database.Conn) error {
	return conn.QueryRow(ctx, `
		select merchant_code
             , terminal_number::text
             , array[environment::text]
             , array[integration::text]
        from redsys
    	where company_id = $1`, company.ID).Scan(
		&f.MerchantCode.Val,
		&f.TerminalNumber.Val,
		&f.Environment.Selected,
		&f.Integration.Selected,
	)
}

func (f *paymentForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
	template.MustRenderAdmin(w, r, user, company, "booking/payment.gohtml", f)
}

func (f *paymentForm) Parse(r *http.Request) error {
	if err := r.ParseForm(); err != nil {
		return err
	}
	f.MerchantCode.FillValue(r)
	f.TerminalNumber.FillValue(r)
	f.Environment.FillValue(r)
	f.Integration.FillValue(r)
	f.EncryptKey.FillValue(r)
	return nil
}

func (f *paymentForm) Valid(l *locale.Locale) bool {
	v := form.NewValidator(l)

	if v.CheckRequired(f.MerchantCode, l.GettextNoop("Merchant code can not be empty.")) {
		if v.CheckExactLength(f.MerchantCode, 9, l.GettextNoop("Merchant code must be exactly nine digits long.")) {
			v.CheckValidInteger(f.MerchantCode, l.GettextNoop("Merchant code must be a number."))
		}
	}

	if v.CheckRequired(f.TerminalNumber, l.GettextNoop("Terminal number can not be empty.")) {
		message := l.GettextNoop("Terminal number must be a number between 1 and 999.")
		if v.CheckValidInteger(f.TerminalNumber, message) {
			if v.CheckMinInteger(f.TerminalNumber, 1, message) {
				v.CheckMaxInteger(f.TerminalNumber, 999, message)
			}
		}
	}

	v.CheckSelectedOptions(f.Environment, l.GettextNoop("Selected environment is not valid."))
	v.CheckSelectedOptions(f.Integration, l.GettextNoop("Selected integration is not valid."))

	if f.EncryptKey.Val != "" {
		v.CheckValidBase64(f.EncryptKey, l.GettextNoop("The merchant key is not valid."))
	}

	return v.AllOK
}
func updatePaymentSettings(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
	f := newPaymentForm(user.Locale)
	if ok, err := form.Handle(f, w, r, user); err != nil {
		return
	} else if !ok {
		f.MustRender(w, r, user, company)
		return
	}
	if err := conn.SetupRedsys(r.Context(), company.ID, f.MerchantCode.Val, f.TerminalNumber.Int(), f.Environment.Selected[0], f.Integration.Selected[0], f.EncryptKey.Val); err != nil {
		panic(err)
	}
	httplib.Redirect(w, r, "/admin/booking/payment", http.StatusSeeOther)
}
