package booking

import (
	"context"
	"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"
	"github.com/jackc/pgx/v4"
	"math"
	"net/http"
	"time"
)

func serveCheckInForm(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, slug string) {
	f := newCheckinForm(slug)
	if err := f.FillFromDatabase(r.Context(), conn, user.Locale); err != nil {
		panic(err)
	}
	f.MustRender(w, r, user, company)
}

func serveGuestForm(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, slug string) {
	f, err := newGuestForm(r.Context(), conn, user.Locale, slug)
	if err != nil {
		panic(err)
	}
	f.MustRender(w, r, user, company)
}

func checkInBooking(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, slug string) {
	f := newCheckinForm(slug)
	if err := f.Parse(r, user, conn); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	if err := user.VerifyCSRFToken(r); err != nil {
		http.Error(w, err.Error(), http.StatusForbidden)
		return
	}
	if ok, err := f.Valid(r.Context(), conn, user.Locale); err != nil {
		panic(err)
	} else if !ok {
		if !httplib.IsHTMxRequest(r) {
			w.WriteHeader(http.StatusUnprocessableEntity)
		}
		f.MustRender(w, r, user, company)
	}

	guests := make([]*database.CheckedInGuest, 0, len(f.Guests))
	for _, g := range f.Guests {
		guests = append(guests, g.checkedInGuest())
	}
	if err := conn.CheckInGuests(r.Context(), f.Slug, guests); err != nil {
		panic(err)
	}
	httplib.Redirect(w, r, "/admin/bookings", http.StatusSeeOther)
}

type checkInForm struct {
	Slug   string
	Guests []*guestForm
}

func newCheckinForm(slug string) *checkInForm {
	return &checkInForm{
		Slug: slug,
	}
}

func (f *checkInForm) FillFromDatabase(ctx context.Context, conn *database.Conn, l *locale.Locale) error {
	documentTypes := form.MustGetDocumentTypeOptions(ctx, conn, l)
	sexes := mustGetSexOptions(ctx, conn, l)
	countries := form.MustGetCountryOptions(ctx, conn, l)

	rows, err := conn.Query(ctx, `
	select array[id_document_type_id]
	     , id_document_number
	     , coalesce(id_document_issue_date::text, '')
	     , given_name
	     , first_surname
	     , second_surname
	     , array[sex_id]
	     , birthdate::text
	     , array[guest.country_code::text]
	     , coalesce(guest.phone::text, '')
	     , guest.address
	from booking_guest as guest
		join booking using (booking_id)
	where slug = $1
`, f.Slug)
	if err != nil {
		return err
	}
	defer rows.Close()
	for rows.Next() {
		guest := newGuestFormWithOptions(documentTypes, sexes, countries, "", nil)
		if err := guest.FillFromRow(rows); err != nil {
			return err
		}
		f.Guests = append(f.Guests, guest)
	}
	if len(f.Guests) == 0 {
		var numberGuests int
		var address string
		var country []string
		row := conn.QueryRow(ctx, "select number_adults + number_teenagers, coalesce(address, ''), array[coalesce(country_code, '')] from booking where slug = $1", f.Slug)
		if err = row.Scan(&numberGuests, &address, &country); err != nil {
			return err
		}
		guests := make([]*guestForm, 0, numberGuests)
		for i := 0; i < numberGuests; i++ {
			guests = append(guests, newGuestFormWithOptions(documentTypes, sexes, countries, address, country))
		}
		f.Guests = guests
	}
	return nil
}

func mustGetSexOptions(ctx context.Context, conn *database.Conn, l *locale.Locale) []*form.Option {
	return form.MustGetOptions(ctx, conn, "select sex.sex_id::text, coalesce(i18n.name, sex.name) as l10n_name from sex left join sex_i18n as i18n on sex.sex_id = i18n.sex_id and i18n.lang_tag = $1 order by l10n_name", l.Language)
}

func (f *checkInForm) Parse(r *http.Request, user *auth.User, conn *database.Conn) error {
	if err := r.ParseForm(); err != nil {
		return err
	}

	documentTypes := form.MustGetDocumentTypeOptions(r.Context(), conn, user.Locale)
	sexes := mustGetSexOptions(r.Context(), conn, user.Locale)
	countries := form.MustGetCountryOptions(r.Context(), conn, user.Locale)

	guest := newGuestFormWithOptions(documentTypes, sexes, countries, "", nil)
	count := guest.count(r)
	f.Guests = make([]*guestForm, 0, count)
	guest.FillValueIndex(r, 0)
	f.Guests = append(f.Guests, guest)
	for i := 1; i < count; i++ {
		guest = newGuestFormWithOptions(documentTypes, sexes, countries, "", nil)
		guest.FillValueIndex(r, i)
		f.Guests = append(f.Guests, guest)
	}

	return nil
}

func (f *checkInForm) Valid(ctx context.Context, conn *database.Conn, l *locale.Locale) (bool, error) {
	allOK := true
	for _, g := range f.Guests {
		if ok, err := g.Valid(ctx, conn, l); err != nil {
			return false, err
		} else if !ok {
			allOK = false
		}
	}
	return allOK, nil
}

func (f *checkInForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
	template.MustRenderAdminFiles(w, r, user, company, f, "booking/checkin.gohtml", "booking/guest.gohtml")
}

type guestForm struct {
	IDDocumentType   *form.Select
	IDDocumentNumber *form.Input
	IDDocumentDate   *form.Input
	GivenName        *form.Input
	FirstSurname     *form.Input
	SecondSurname    *form.Input
	Sex              *form.Select
	Birthdate        *form.Input
	Country          *form.Select
	Address          *form.Input
	Phone            *form.Input
}

func newGuestForm(ctx context.Context, conn *database.Conn, l *locale.Locale, slug string) (*guestForm, error) {
	documentTypes := form.MustGetDocumentTypeOptions(ctx, conn, l)
	sexes := mustGetSexOptions(ctx, conn, l)
	countries := form.MustGetCountryOptions(ctx, conn, l)

	var address string
	var country []string
	row := conn.QueryRow(ctx, "select coalesce(address, ''), array[coalesce(country_code, '')] from booking where slug = $1", slug)
	if err := row.Scan(&address, &country); err != nil {
		return nil, err
	}
	return newGuestFormWithOptions(documentTypes, sexes, countries, address, country), nil
}

func newGuestFormWithOptions(documentTypes []*form.Option, sexes []*form.Option, countries []*form.Option, address string, selectedCountry []string) *guestForm {
	return &guestForm{
		IDDocumentType: &form.Select{
			Name:    "id_document_type",
			Options: documentTypes,
		},
		IDDocumentNumber: &form.Input{
			Name: "id_document_number",
		},
		IDDocumentDate: &form.Input{
			Name: "id_document_date",
		},
		GivenName: &form.Input{
			Name: "given_name",
		},
		FirstSurname: &form.Input{
			Name: "first_surname",
		},
		SecondSurname: &form.Input{
			Name: "second_surname",
		},
		Sex: &form.Select{
			Name:    "sex",
			Options: sexes,
		},
		Birthdate: &form.Input{
			Name: "birthdate",
		},
		Country: &form.Select{
			Name:     "country",
			Options:  countries,
			Selected: selectedCountry,
		},
		Address: &form.Input{
			Name: "address",
			Val:  address,
		},
		Phone: &form.Input{
			Name: "phone",
		},
	}
}

func (f *guestForm) count(r *http.Request) int {
	keys := []string{f.IDDocumentType.Name, f.IDDocumentNumber.Name, f.IDDocumentDate.Name, f.GivenName.Name, f.FirstSurname.Name, f.SecondSurname.Name, f.Sex.Name, f.Birthdate.Name, f.Country.Name, f.Address.Name, f.Phone.Name}
	min := math.MaxInt
	for _, key := range keys {
		l := len(r.Form[key])
		if len(r.Form[key]) < min {
			min = l
		}
	}
	return min
}

func (f *guestForm) FillValueIndex(r *http.Request, idx int) {
	f.IDDocumentType.FillValueIndex(r, idx)
	f.IDDocumentNumber.FillValueIndex(r, idx)
	f.IDDocumentDate.FillValueIndex(r, idx)
	f.GivenName.FillValueIndex(r, idx)
	f.FirstSurname.FillValueIndex(r, idx)
	f.SecondSurname.FillValueIndex(r, idx)
	f.Sex.FillValueIndex(r, idx)
	f.Birthdate.FillValueIndex(r, idx)
	f.Country.FillValueIndex(r, idx)
	f.Address.FillValueIndex(r, idx)
	f.Phone.FillValueIndex(r, idx)
}

func (f *guestForm) FillFromRow(row pgx.Rows) error {
	return row.Scan(
		&f.IDDocumentType.Selected,
		&f.IDDocumentNumber.Val,
		&f.IDDocumentDate.Val,
		&f.GivenName.Val,
		&f.FirstSurname.Val,
		&f.SecondSurname.Val,
		&f.Sex.Selected,
		&f.Birthdate.Val,
		&f.Country.Selected,
		&f.Phone.Val,
		&f.Address.Val,
	)
}

func (f *guestForm) Valid(ctx context.Context, conn *database.Conn, l *locale.Locale) (bool, error) {
	v := form.NewValidator(l)

	today := time.Now()
	yesterday := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, time.UTC)

	v.CheckSelectedOptions(f.IDDocumentType, l.GettextNoop("Selected ID document type is not valid."))
	v.CheckRequired(f.IDDocumentNumber, l.GettextNoop("ID document number can not be empty."))
	if f.IDDocumentDate.Val != "" {
		if v.CheckValidDate(f.IDDocumentDate, l.GettextNoop("ID document issue date must be a valid date.")) {
			v.CheckMaxDate(f.IDDocumentDate, yesterday, l.Gettext("ID document issue date must be in the past."))
		}
	}
	v.CheckRequired(f.GivenName, l.GettextNoop("Full name can not be empty."))
	v.CheckRequired(f.FirstSurname, l.GettextNoop("Full name can not be empty."))
	v.CheckSelectedOptions(f.Sex, l.GettextNoop("Selected sex is not valid."))
	if v.CheckRequired(f.Birthdate, l.GettextNoop("Birthdate can not be empty")) {
		if v.CheckValidDate(f.Birthdate, l.GettextNoop("Birthdate must be a valid date.")) {
			v.CheckMaxDate(f.Birthdate, yesterday, l.Gettext("Birthdate must be in the past."))
		}
	}
	var country string
	if v.CheckSelectedOptions(f.Country, l.GettextNoop("Selected country is not valid.")) {
		country = f.Country.Selected[0]
	}
	if f.Phone.Val != "" && country != "" {
		if _, err := v.CheckValidPhone(ctx, conn, f.Phone, country, l.GettextNoop("This phone number is not valid.")); err != nil {
			return false, err
		}
	}

	return v.AllOK, nil
}

func (f *guestForm) checkedInGuest() *database.CheckedInGuest {
	birthdate, err := time.Parse(database.ISODateFormat, f.Birthdate.Val)
	if err != nil {
		panic(err)
	}
	issueDate, err := time.Parse(database.ISODateFormat, f.IDDocumentDate.Val)
	if err != nil {
		issueDate = time.Time{}
	}
	return &database.CheckedInGuest{
		IDDocumentType:      f.IDDocumentType.String(),
		IDDocumentNumber:    f.IDDocumentNumber.Val,
		IDDocumentIssueDate: issueDate,
		GivenName:           f.GivenName.Val,
		FirstSurname:        f.FirstSurname.Val,
		SecondSurname:       f.SecondSurname.Val,
		Sex:                 f.Sex.String(),
		Birthdate:           birthdate,
		CountryCode:         f.Country.String(),
		Phone:               f.Phone.Val,
		Address:             f.Address.Val,
	}
}

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