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

package types

import (
	"context"
	"github.com/jackc/pgx/v4"
	gotemplate "html/template"
	"net/http"
	"time"

	"golang.org/x/text/language"

	"dev.tandem.ws/tandem/camper/pkg/auth"
	"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)
			}
		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
	Features       []*typeFeature
	Calendar       *season.Calendar
	Spiel          gotemplate.HTML
	Info           gotemplate.HTML
	Facilities     gotemplate.HTML
	Description    gotemplate.HTML
	AdditionalInfo gotemplate.HTML
}

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

type optionPrice struct {
	OptionName    string
	PricePerNight string
}

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) {
	prices, err := collectPrices(ctx, conn, loc.Language, slug)
	if err != nil {
		return nil, err
	}
	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,
		Prices:     prices,
		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
		     , dogs_allowed
		     , '3.50' as dogs_prices
		from campsite_type
		    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.DogsAllowed, &page.DogsPrice); err != nil {
		return nil, err
	}
	if err := conn.QueryRow(ctx, "select to_price(tourist_tax, $1) from company where company_id = $2", company.DecimalDigits, company.ID).Scan(&page.TouristTax); err != nil {
		return nil, err
	}

	return page, nil
}

func collectPrices(ctx context.Context, conn *database.Conn, language language.Tag, slug string) ([]*typePrice, error) {
	rows, err := conn.Query(ctx, `
		select coalesce(i18n.name, season.name) as l10n_name
		     , to_color(season.color)::text
		     , coalesce(cost.min_nights, 1)
		     , to_price(coalesce(cost.cost_per_night, 0))
		     , array_agg((coalesce(option_i18n.name, option.name), to_price(coalesce(option_cost.cost_per_night, 0))) 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.min_nights
		       , cost.cost_per_night
		       , 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 options database.RecordArray
		if err := rows.Scan(&price.SeasonName, &price.SeasonColor, &price.MinNights, &price.PricePerNight, &options); err != nil {
			return nil, err
		}
		for _, el := range options.Elements {
			price.Options = append(price.Options, &optionPrice{
				OptionName:    el.Fields[0].Get().(string),
				PricePerNight: el.Fields[1].Get().(string),
			})
		}
		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")
}
