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

package template

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

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

type PublicPage struct {
	LocalizedAlternates []*LocalizedAlternate
	Menu                *siteMenu
	CompanyAddress      *address
	WeatherForecast     *WeatherForecast
	OpeningDates        gotemplate.HTML
}

func NewPublicPage() *PublicPage {
	return &PublicPage{
		CompanyAddress:  &address{},
		WeatherForecast: &WeatherForecast{},
	}
}

func (p *PublicPage) Setup(r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
	schema := httplib.Protocol(r)
	authority := httplib.Host(r)
	_, path := httplib.ShiftPath(r.RequestURI)
	for _, l := range company.Locales {
		p.LocalizedAlternates = append(p.LocalizedAlternates, &LocalizedAlternate{
			Lang:    l.Language.String(),
			Endonym: l.Endonym,
			HRef:    fmt.Sprintf("%s://%s/%s%s", schema, authority, l.Language, path),
		})
	}
	sort.Slice(p.LocalizedAlternates, func(i, j int) bool { return p.LocalizedAlternates[i].Lang < p.LocalizedAlternates[j].Lang })

	p.Menu = &siteMenu{
		CampsiteTypes: mustCollectMenuItems(r.Context(), conn, user.Locale, `
			select coalesce(i18n.name, campsite_type.name) as l10n_name
			     , '/campsites/types/' || slug
			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 company_id = $2 
			  and active
			order by position, l10n_name
		`, user.Locale.Language, company.ID),
	}

	if err := conn.QueryRow(r.Context(), `
		select coalesce(i18n.opening_dates, location.opening_dates)
		from location
			left join location_i18n as i18n on location.company_id = i18n.company_id and i18n.lang_tag = $1
		where location.company_id = $2
	`, user.Locale.Language, company.ID).Scan(&p.OpeningDates); err != nil {
		if !database.ErrorIsNotFound(err) {
			panic(err)
		}
	}

	if err := p.CompanyAddress.FillFromDatabase(r.Context(), conn, user, company); err != nil {
		panic(err)
	}

	if err := p.WeatherForecast.FillFromDatabase(r.Context(), conn); err != nil {
		panic(err)
	}
}

type LocalizedAlternate struct {
	Lang    string
	HRef    string
	Endonym string
}

type siteMenu struct {
	CampsiteTypes []*menuItem
}

type menuItem struct {
	Label string
	HRef  string
}

func mustCollectMenuItems(ctx context.Context, conn *database.Conn, loc *locale.Locale, sql string, args ...interface{}) []*menuItem {
	rows, err := conn.Query(ctx, sql, args...)
	if err != nil {
		panic(err)
	}
	defer rows.Close()

	localePath := "/" + loc.Language.String()
	var items []*menuItem
	for rows.Next() {
		item := &menuItem{}
		err = rows.Scan(&item.Label, &item.HRef)
		if err != nil {
			panic(err)
		}
		item.HRef = localePath + item.HRef
		items = append(items, item)
	}
	if rows.Err() != nil {
		panic(rows.Err())
	}

	return items
}

type address struct {
	TradeName  string
	Address    string
	PostalCode string
	Province   string
	City       string
	Country    string
	Phone      string
	Email      string
	RTCNumber  string
}

func (addr *address) FillFromDatabase(ctx context.Context, conn *database.Conn, user *auth.User, company *auth.Company) error {
	row := conn.QueryRow(ctx, `
select trade_name
, address
, postal_code
, province
, city
, coalesce(country_i18n.name, country.name) as country_name
, phone::text
, email::text
, rtc_number
from company
join country using (country_code)
left join country_i18n on country.country_code = country_i18n.country_code and country_i18n.lang_tag = $2
where company_id = $1
`, company.ID, user.Locale.Language)
	return row.Scan(
		&addr.TradeName,
		&addr.Address,
		&addr.PostalCode,
		&addr.Province,
		&addr.City,
		&addr.Country,
		&addr.Phone,
		&addr.Email,
		&addr.RTCNumber,
	)
}

type WeatherForecast struct {
	WeatherConditionId string
	DayTemperature     string
	MinTemperature     string
	ForecastedAt       time.Time
}

func (fc *WeatherForecast) FillFromDatabase(ctx context.Context, conn *database.Conn) error {
	row := conn.QueryRow(ctx, `
select weather_condition_id
     , ceil(day_temperature) || '°'
     , ceil(min_temperature) || '° C'
     , forecasted_at
from weather_forecast
limit 1
`)
	return row.Scan(
		&fc.WeatherConditionId,
		&fc.DayTemperature,
		&fc.MinTemperature,
		&fc.ForecastedAt,
	)
}
