package booking

import (
	"context"
	"dev.tandem.ws/tandem/camper/pkg/auth"
	"dev.tandem.ws/tandem/camper/pkg/database"
	"time"

	"dev.tandem.ws/tandem/camper/pkg/season"
)

type Month struct {
	Year  int
	Month time.Month
	Name  string
	Days  []time.Time
	Spans []*Span
}

type Span struct {
	Weekend bool
	Today   bool
	Count   int
}

func isWeekend(t time.Time) bool {
	switch t.Weekday() {
	case time.Saturday, time.Sunday:
		return true
	default:
		return false
	}
}

func CollectMonths(from time.Time, to time.Time) []*Month {
	current := time.Date(from.Year(), from.Month(), from.Day(), 0, 0, 0, 0, time.UTC)
	now := time.Now()
	today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC)
	var months []*Month
	for !current.Equal(to) {
		span := &Span{
			Weekend: isWeekend(current),
			Today:   current.Equal(today),
		}
		month := &Month{
			Year:  current.Year(),
			Month: current.Month(),
			Name:  season.LongMonthNames[current.Month()-1],
			Days:  make([]time.Time, 0, 31),
			Spans: make([]*Span, 0, 10),
		}
		month.Spans = append(month.Spans, span)
		for current.Month() == month.Month && !current.Equal(to) {
			month.Days = append(month.Days, current)
			if span.Weekend != isWeekend(current) || span.Today != current.Equal(today) {
				span = &Span{
					Weekend: isWeekend(current),
					Today:   current.Equal(today),
				}
				month.Spans = append(month.Spans, span)
			}
			span.Count = span.Count + 1
			current = current.AddDate(0, 0, 1)
		}
		months = append(months, month)
	}
	return months
}

type CampsiteEntry struct {
	ID       int
	Label    string
	Type     string
	TypeSlug string
	Active   bool
	Selected bool
	Bookings map[time.Time]*CampsiteBooking
}

type CampsiteBooking struct {
	URL    string
	Holder string
	Status string
	Nights int
	Begin  bool
	End    bool
}

func CollectCampsiteEntries(ctx context.Context, company *auth.Company, conn *database.Conn, from time.Time, to time.Time, campsiteType string) ([]*CampsiteEntry, error) {
	rows, err := conn.Query(ctx, `
		select campsite_id
             , campsite.label
		     , campsite_type.name
		     , campsite_type.slug
		     , campsite.active
		from campsite
		join campsite_type using (campsite_type_id)
		where campsite.company_id = $1
		  and ($2::uuid is null or campsite_type.slug = $2::uuid)
		order by label`, company.ID, database.ZeroNullUUID(campsiteType))
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	byLabel := make(map[string]*CampsiteEntry)
	var campsites []*CampsiteEntry
	for rows.Next() {
		entry := &CampsiteEntry{}
		if err = rows.Scan(&entry.ID, &entry.Label, &entry.Type, &entry.TypeSlug, &entry.Active); err != nil {
			return nil, err
		}
		campsites = append(campsites, entry)
		byLabel[entry.Label] = entry
	}

	if err := collectCampsiteBookings(ctx, company, conn, from, to, byLabel); err != nil {
		return nil, err
	}

	return campsites, nil
}

func collectCampsiteBookings(ctx context.Context, company *auth.Company, conn *database.Conn, from time.Time, to time.Time, campsites map[string]*CampsiteEntry) error {
	rows, err := conn.Query(ctx, `
		select campsite.label
		     , lower(booking_campsite.stay * daterange($2::date, $3::date))
		     , '/admin/bookings/' || booking.slug
		     , holder_name
		     , booking_status
			 , upper(booking_campsite.stay * daterange($2::date, $3::date)) - lower(booking_campsite.stay * daterange($2::date, $3::date))
			 , booking_campsite.stay &> daterange($2::date, $3::date) 
			 , booking_campsite.stay &< daterange($2::date, ($3 - 1)::date) 
		from booking_campsite
		join booking using (booking_id)
		join campsite using (campsite_id)
		where booking.company_id = $1
		  and booking_campsite.stay && daterange($2::date, $3::date)
		order by label`, company.ID, from, to)
	if err != nil {
		return err
	}
	defer rows.Close()

	for rows.Next() {
		entry := &CampsiteBooking{}
		var label string
		var date time.Time
		if err = rows.Scan(&label, &date, &entry.URL, &entry.Holder, &entry.Status, &entry.Nights, &entry.Begin, &entry.End); err != nil {
			return err
		}
		campsite := campsites[label]
		if campsite != nil {
			if campsite.Bookings == nil {
				campsite.Bookings = make(map[time.Time]*CampsiteBooking)
			}
			campsite.Bookings[date] = entry
		}
	}

	return nil
}
