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

package types

import (
	"context"
	gotemplate "html/template"
	"net/http"
	"time"

	"github.com/jackc/pgx/v4"
	"golang.org/x/text/language"

	"dev.tandem.ws/tandem/camper/pkg/auth"
	"dev.tandem.ws/tandem/camper/pkg/booking"
	"dev.tandem.ws/tandem/camper/pkg/carousel"
	"dev.tandem.ws/tandem/camper/pkg/database"
	httplib "dev.tandem.ws/tandem/camper/pkg/http"
	"dev.tandem.ws/tandem/camper/pkg/locale"
	"dev.tandem.ws/tandem/camper/pkg/season"
	"dev.tandem.ws/tandem/camper/pkg/template"
	"dev.tandem.ws/tandem/camper/pkg/uuid"
)

type PublicHandler struct {
}

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

		if !uuid.Valid(typeUuid) {
			http.NotFound(w, r)
			return
		}

		var head string
		head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
		switch head {
		case "":
			switch r.Method {
			case http.MethodGet:
				page, err := newPublicPage(r.Context(), company, conn, user.Locale, typeUuid)
				if database.ErrorIsNotFound(err) {
					http.NotFound(w, r)
					return
				} else if err != nil {
					panic(err)
				}
				page.MustRender(w, r, user, company, conn)
			default:
				httplib.MethodNotAllowed(w, r, http.MethodGet)
			}
		case "dates":
			switch r.Method {
			case http.MethodGet:
				bookingDates, err := booking.NewDateFields(r.Context(), conn, typeUuid)
				if err != nil {
					panic(err)
				}
				if err := r.ParseForm(); err != nil {
					panic(err)
				}
				bookingDates.FillValues(r, user.Locale)
				template.MustRenderPublicNoLayout(w, r, user, company, "campsite/dates.gohtml", bookingDates)
			default:
				httplib.MethodNotAllowed(w, r, http.MethodGet)
			}
		default:
			http.NotFound(w, r)
		}
	})
}

type publicPage struct {
	*template.PublicPage
	Slug           string
	Name           string
	DogsAllowed    bool
	Carousel       []*carousel.Slide
	Prices         []*typePrice
	DogsPrice      string
	TouristTax     string
	TouristTaxDays int
	Features       []*typeFeature
	Calendar       *season.Calendar
	Spiel          gotemplate.HTML
	Info           gotemplate.HTML
	Facilities     gotemplate.HTML
	Description    gotemplate.HTML
	AdditionalInfo gotemplate.HTML
	CheckIn        string
	CheckOut       string
	BookingDates   *booking.DateFields
}

type typePrice struct {
	SeasonName  string
	SeasonColor string
	Options     []*optionPrice
}

type optionPrice struct {
	OptionName string
	Price      string
	PerNight   bool
}

type typeFeature struct {
	Icon string
	Name string
}

func newPublicPage(ctx context.Context, company *auth.Company, conn *database.Conn, loc *locale.Locale, slug string) (*publicPage, error) {
	features, err := collectFeatures(ctx, conn, loc.Language, slug)
	if err != nil {
		return nil, err
	}
	now := time.Now()
	calendar, err := season.CollectSeasonCalendar(ctx, company, conn, now.Year(), now.Month(), 6)
	if err != nil {
		return nil, err
	}
	page := &publicPage{
		PublicPage: template.NewPublicPage(),
		Slug:       slug,
		Carousel:   mustCollectSlides(ctx, conn, loc, slug),
		Calendar:   calendar,
		Features:   features,
	}
	row := conn.QueryRow(ctx, `
		select coalesce(i18n.name, campsite_type.name) as l10n_name
		     , coalesce(i18n.spiel, campsite_type.spiel)::text as l10n_description
		     , coalesce(i18n.info, campsite_type.info)::text as l10n_info
		     , coalesce(i18n.facilities, campsite_type.facilities)::text as l10n_facilities
		     , coalesce(i18n.description, campsite_type.description)::text as l10n_description
		     , coalesce(i18n.additional_info, campsite_type.additional_info)::text as l10n_description
		     , coalesce(i18n.check_in, campsite_type.check_in)::text as l10n_check_in
		     , coalesce(i18n.check_out, campsite_type.check_out)::text as l10n_check_out
		     , pet.cost_per_night is not null as dogs_allowed
		     , to_price(coalesce(pet.cost_per_night, 0)) as dogs_prices
		from campsite_type
		    left join campsite_type_pet_cost as pet using (campsite_type_id)
		    left join campsite_type_i18n as i18n on campsite_type.campsite_type_id = i18n.campsite_type_id and i18n.lang_tag = $1
		where slug = $2
		  and active
	`, loc.Language, slug)
	if err = row.Scan(
		&page.Name,
		&page.Spiel,
		&page.Info,
		&page.Facilities,
		&page.Description,
		&page.AdditionalInfo,
		&page.CheckIn,
		&page.CheckOut,
		&page.DogsAllowed,
		&page.DogsPrice,
	); err != nil {
		return nil, err
	}
	if err = conn.QueryRow(ctx, "select to_price(tourist_tax, $1), tourist_tax_max_days from company where company_id = $2", company.DecimalDigits, company.ID).Scan(&page.TouristTax, &page.TouristTaxDays); err != nil {
		return nil, err
	}
	page.BookingDates, err = booking.NewDateFields(ctx, conn, slug)
	if err != nil {
		return nil, err
	}
	page.Prices, err = collectPrices(ctx, conn, loc.Language, slug, page.Name)
	if err != nil {
		return nil, err
	}

	return page, nil
}

func collectPrices(ctx context.Context, conn *database.Conn, language language.Tag, slug string, campsiteName string) ([]*typePrice, error) {
	rows, err := conn.Query(ctx, `
		select coalesce(i18n.name, season.name) as l10n_name
		     , to_color(season.color)::text
		     , case when cost.cost_per_night > 0 then to_price(coalesce(cost.cost_per_night, 0)) else '' end
		     , case when cost.cost_per_adult > 0 then to_price(coalesce(cost.cost_per_adult, 0)) else '' end
		     , case when cost.cost_per_teenager > 0 then to_price(coalesce(cost.cost_per_teenager, 0)) else '' end
		     , case when cost.cost_per_child > 0 then to_price(coalesce(cost.cost_per_child, 0)) else '' end
		     , array_agg((coalesce(option_i18n.name, option.name), to_price(coalesce(option_cost.cost, 0)), option.per_night) order by option.position) filter (where option.campsite_type_option_id is not null)
        from season
		    left join season_i18n as i18n on season.season_id = i18n.season_id and i18n.lang_tag = $1
			left join (
			    campsite_type_cost as cost join campsite_type as type on cost.campsite_type_id = type.campsite_type_id and type.slug = $2
		    ) as cost on cost.season_id = season.season_id
        	left join (
        	     select option.*
        	    from campsite_type_option as option
        	        join campsite_type as type on option.campsite_type_id = type.campsite_type_id and type.slug = $2
   	        ) as option on true
        	left join campsite_type_option_i18n as option_i18n on option_i18n.campsite_type_option_id = option.campsite_type_option_id and option_i18n.lang_tag = $1
        	left join campsite_type_option_cost as option_cost on option_cost.campsite_type_option_id = option.campsite_type_option_id and option_cost.season_id = season.season_id
		where season.active
		group by i18n.name
		       , season.name
		       , season.color
		       , cost.cost_per_night
		       , cost.cost_per_adult
		       , cost.cost_per_teenager
		       , cost.cost_per_child
		       , season.position
		order by season.position, l10n_name
    `, pgx.QueryResultFormats{pgx.BinaryFormatCode}, language, slug)
	if err != nil {
		return nil, err
	}

	var prices []*typePrice
	for rows.Next() {
		price := &typePrice{}
		var pricePerNight string
		var pricePerAdult string
		var pricePerTeenager string
		var pricePerChild string
		var options database.RecordArray
		if err = rows.Scan(
			&price.SeasonName,
			&price.SeasonColor,
			&pricePerNight,
			&pricePerAdult,
			&pricePerTeenager,
			&pricePerChild,
			&options,
		); err != nil {
			return nil, err
		}
		if pricePerNight != "" {
			option := &optionPrice{
				Price:    pricePerNight,
				PerNight: true,
			}
			if pricePerAdult != "" || pricePerTeenager != "" || pricePerChild != "" || len(options.Elements) > 0 {
				option.OptionName = campsiteName
			}
			price.Options = append(price.Options, option)
		}
		if pricePerAdult != "" {
			price.Options = append(price.Options, &optionPrice{
				OptionName: locale.PgettextNoop("Adults", "header"),
				Price:      pricePerAdult,
				PerNight:   true,
			})
		}
		if pricePerTeenager != pricePerAdult && pricePerTeenager != "" {
			price.Options = append(price.Options, &optionPrice{
				OptionName: locale.PgettextNoop("Teenagers (aged 11 to 16)", "header"),
				Price:      pricePerTeenager,
				PerNight:   true,
			})
		}
		if pricePerChild != "" {
			price.Options = append(price.Options, &optionPrice{
				OptionName: locale.PgettextNoop("Children (aged 2 to 10)", "header"),
				Price:      pricePerChild,
				PerNight:   true,
			})
		}
		for _, el := range options.Elements {
			price.Options = append(price.Options, &optionPrice{
				OptionName: el.Fields[0].Get().(string),
				Price:      el.Fields[1].Get().(string),
				PerNight:   el.Fields[2].Get().(bool),
			})
		}
		prices = append(prices, price)
	}
	return prices, nil
}

func collectFeatures(ctx context.Context, conn *database.Conn, language language.Tag, slug string) ([]*typeFeature, error) {
	rows, err := conn.Query(ctx, `
		select feature.icon_name
		     , coalesce(i18n.name, feature.name) as l10n_name
        from campsite_type_feature as feature
            join campsite_type using (campsite_type_id)
		    left join campsite_type_feature_i18n as i18n on feature.campsite_type_feature_id = i18n.campsite_type_feature_id and i18n.lang_tag = $1
		where campsite_type.slug = $2
		order by feature.position
    `, language, slug)
	if err != nil {
		return nil, err
	}

	var features []*typeFeature
	for rows.Next() {
		feature := &typeFeature{}
		if err := rows.Scan(&feature.Icon, &feature.Name); err != nil {
			return nil, err
		}
		features = append(features, feature)
	}
	return features, nil
}

func (p *publicPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
	p.Setup(r, user, company, conn)
	template.MustRenderPublicFiles(w, r, user, company, p, "campsite/type.gohtml", "campsite/calendar.gohtml", "campsite/dates.gohtml")
}
