package templates

import (
	"fmt"
	"time"

	"backend/internal/db/sqlc"
)

type PlanningAgenda struct {
	Start        time.Time
	End          time.Time
	Today        time.Time
	RangeLabel   string
	PrevURL      string
	TodayURL     string
	NextURL      string
	ShowingToday bool
	Events       []PlanningEventRow
}

type PlanningEventRow struct {
	Title       string
	DateLabel   string
	TimeRange   string
	TabloTitle  string
	TabloColor  string
	HasColor    bool
	Location    string
	HasLocation bool
	URL         string
}

func NewPlanningAgenda(start time.Time, end time.Time, today time.Time, rows []sqlc.ListUserEventsRangeRow) PlanningAgenda {
	events := make([]PlanningEventRow, 0, len(rows))
	for _, row := range rows {
		location := ""
		if row.Location.Valid {
			location = row.Location.String
		}
		color := ""
		if row.TabloColor.Valid {
			color = row.TabloColor.String
		}
		events = append(events, PlanningEventRow{
			Title:       row.Title,
			DateLabel:   PlanningEventDateLabel(row),
			TimeRange:   PlanningEventTimeRange(row),
			TabloTitle:  row.TabloTitle,
			TabloColor:  color,
			HasColor:    color != "",
			Location:    location,
			HasLocation: location != "",
			URL:         PlanningEventURL(row),
		})
	}
	return PlanningAgenda{
		Start:        start,
		End:          end,
		Today:        today,
		RangeLabel:   PlanningRangeLabel(start, end),
		PrevURL:      PlanningURL(start.AddDate(0, 0, -14)),
		TodayURL:     "/planning",
		NextURL:      PlanningURL(start.AddDate(0, 0, 14)),
		ShowingToday: samePlanningDay(start, today),
		Events:       events,
	}
}

func PlanningURL(start time.Time) string {
	return "/planning?start=" + start.Format("2006-01-02")
}

func PlanningEventURL(row sqlc.ListUserEventsRangeRow) string {
	return "/tablos/" + row.TabloID.String() + "/events?month=" + row.EventDate.Time.Format("2006-01")
}

func PlanningEventDateLabel(row sqlc.ListUserEventsRangeRow) string {
	if !row.EventDate.Valid {
		return ""
	}
	return row.EventDate.Time.Format("Jan 2, 2006")
}

func PlanningEventTimeRange(row sqlc.ListUserEventsRangeRow) string {
	start := FormatEventTime(row.StartTime)
	if start == "" {
		return ""
	}
	if !row.EndTime.Valid {
		return start
	}
	return start + "-" + FormatEventTime(row.EndTime)
}

func PlanningRangeLabel(start time.Time, end time.Time) string {
	if start.Year() == end.Year() {
		return start.Format("January 2") + " - " + end.Format("January 2, 2006")
	}
	return start.Format("January 2, 2006") + " - " + end.Format("January 2, 2006")
}

func samePlanningDay(a time.Time, b time.Time) bool {
	return a.Year() == b.Year() && a.Month() == b.Month() && a.Day() == b.Day()
}

// PlanningShowDaySeparator returns true when the event at index i belongs to a
// different day than the previous event — i.e. a date-group header should be
// rendered before it.
func PlanningShowDaySeparator(events []PlanningEventRow, index int) bool {
	if index == 0 {
		return true
	}
	return events[index].DateLabel != events[index-1].DateLabel
}

// ---------------------------------------------------------------------------
// Calendar view data structures
// ---------------------------------------------------------------------------

// CalendarEvent is used in the month grid cells.
type CalendarEvent struct {
	Title      string
	ColorClass string
	Style      string // inline style for background/color when tablo has a color
	URL        string
	TimeLabel  string // "10:00" or "" if all-day
}

// CalendarDay is one cell in the month grid.
type CalendarDay struct {
	Date       time.Time
	DayNum     int
	IsToday    bool
	OtherMonth bool
	Events     []CalendarEvent
}

// CalendarTimeEvent is an event placed on the hour-timeline (week/day views).
type CalendarTimeEvent struct {
	Title      string
	ColorClass string
	Style      string
	URL        string
	TopPx      int
	HeightPx   int
}

// CalendarDayColumn is one day column in the week/day timeline.
type CalendarDayColumn struct {
	Date    time.Time
	Label   string // "Mon 18" (week) or "Monday 18 May" (day)
	IsToday bool
	Events  []CalendarTimeEvent
}

// MiniCalDay is one cell in the mini-month panel used by week/day views.
type MiniCalDay struct {
	Date    time.Time
	DayNum  int
	IsToday bool
	InWeek  bool // highlighted because it is in the viewed week
	URL     string
}

// PlanningCalendar holds all data needed to render any of the three views.
type PlanningCalendar struct {
	View       string // "month", "week", "day"
	Label      string // period label shown in header
	PrevURL    string
	TodayURL   string
	NextURL    string
	// Month view
	Weeks [][]CalendarDay
	// Week / Day views
	Days      []CalendarDayColumn
	HourSlots []string // ["07:00", "08:00", ... "20:00"]
	// Mini-month panel (week/day views)
	MiniMonth      [][]MiniCalDay // 5-6 rows × 7
	MiniMonthLabel string         // "May 2026"
}

// ---------------------------------------------------------------------------
// URL helpers
// ---------------------------------------------------------------------------

func PlanningMonthURL(t time.Time) string {
	return "/planning?view=month&month=" + t.Format("2006-01")
}

func PlanningWeekURL(t time.Time) string {
	monday := MondayOf(t)
	return "/planning?view=week&start=" + monday.Format("2006-01-02")
}

func PlanningDayURL(t time.Time) string {
	return "/planning?view=day&date=" + t.Format("2006-01-02")
}

// MondayOf returns the Monday of the week containing t (ISO week, Mon=first day).
func MondayOf(t time.Time) time.Time {
	wd := int(t.Weekday()) // Sunday=0
	if wd == 0 {
		wd = 7
	}
	return time.Date(t.Year(), t.Month(), t.Day()-wd+1, 0, 0, 0, 0, t.Location())
}

// ---------------------------------------------------------------------------
// Color helper
// ---------------------------------------------------------------------------

// calColorStyle returns an inline style string that tints the event chip using
// the tablo's hex color. When color is empty the ev-default class is used instead.
func calColorStyle(color string) string {
	if color == "" {
		return ""
	}
	return fmt.Sprintf(
		"background:color-mix(in srgb,%s 15%%,transparent);color:%s",
		color, color,
	)
}

// ---------------------------------------------------------------------------
// Calendar builder helpers
// ---------------------------------------------------------------------------

// calEventFromRow converts a raw DB row into a CalendarEvent (month view).
func calEventFromRow(row sqlc.ListUserEventsRangeRow) CalendarEvent {
	color := ""
	if row.TabloColor.Valid {
		color = row.TabloColor.String
	}
	colorClass := "ev-default"
	style := calColorStyle(color)
	if style != "" {
		colorClass = ""
	}
	timeLabel := FormatEventTime(row.StartTime)
	return CalendarEvent{
		Title:      row.Title,
		ColorClass: colorClass,
		Style:      style,
		URL:        PlanningEventURL(row),
		TimeLabel:  timeLabel,
	}
}

// calTimeEventFromRow converts a DB row into a CalendarTimeEvent (week/day view).
func calTimeEventFromRow(row sqlc.ListUserEventsRangeRow) CalendarTimeEvent {
	color := ""
	if row.TabloColor.Valid {
		color = row.TabloColor.String
	}
	colorClass := "ev-default"
	style := calColorStyle(color)
	if style != "" {
		colorClass = ""
	}

	// Compute TopPx and HeightPx from start/end times.
	// Timeline shows 07:00–20:00 (hour offset 0 = 07:00). 48px per hour.
	topPx := 48  // default: 08:00
	heightPx := 24 // default: 30 min
	if row.StartTime.Valid {
		totalMicros := row.StartTime.Microseconds
		hours := int(totalMicros / 3_600_000_000)
		mins := int((totalMicros % 3_600_000_000) / 60_000_000)
		topPx = (hours-7)*48 + mins*48/60
		if topPx < 0 {
			topPx = 0
		}
		heightPx = 24 // default 30 min if no end time
		if row.EndTime.Valid {
			endMicros := row.EndTime.Microseconds
			endHours := int(endMicros / 3_600_000_000)
			endMins := int((endMicros % 3_600_000_000) / 60_000_000)
			durationMins := (endHours*60 + endMins) - (hours*60 + mins)
			if durationMins < 0 {
				durationMins = 30
			}
			heightPx = durationMins * 48 / 60
		}
	}
	if heightPx < 20 {
		heightPx = 20
	}
	return CalendarTimeEvent{
		Title:      row.Title,
		ColorClass: colorClass,
		Style:      style,
		URL:        PlanningEventURL(row),
		TopPx:      topPx,
		HeightPx:   heightPx,
	}
}

// BuildMonthCalendar constructs a PlanningCalendar for the month view.
func BuildMonthCalendar(month time.Time, today time.Time, rows []sqlc.ListUserEventsRangeRow) PlanningCalendar {
	// Index events by date
	byDate := map[string][]CalendarEvent{}
	for _, row := range rows {
		if !row.EventDate.Valid {
			continue
		}
		key := row.EventDate.Time.Format("2006-01-02")
		byDate[key] = append(byDate[key], calEventFromRow(row))
	}

	// First day of month, last day of month
	first := time.Date(month.Year(), month.Month(), 1, 0, 0, 0, 0, month.Location())
	last := first.AddDate(0, 1, -1)

	// Start grid from the Monday on or before 1st of month
	gridStart := MondayOf(first)

	// Build 6 weeks (42 days) to ensure we always cover the month
	var weeks [][]CalendarDay
	cursor := gridStart
	for w := 0; w < 6; w++ {
		var week []CalendarDay
		for d := 0; d < 7; d++ {
			key := cursor.Format("2006-01-02")
			week = append(week, CalendarDay{
				Date:       cursor,
				DayNum:     cursor.Day(),
				IsToday:    samePlanningDay(cursor, today),
				OtherMonth: cursor.Month() != first.Month(),
				Events:     byDate[key],
			})
			cursor = cursor.AddDate(0, 0, 1)
		}
		weeks = append(weeks, week)
		// Stop after we've passed the last day of the month and completed the week
		if cursor.After(last) && len(weeks) >= 4 {
			break
		}
	}

	prevMonth := first.AddDate(0, -1, 0)
	nextMonth := first.AddDate(0, 1, 0)

	return PlanningCalendar{
		View:     "month",
		Label:    first.Format("January 2006"),
		PrevURL:  PlanningMonthURL(prevMonth),
		TodayURL: PlanningMonthURL(today),
		NextURL:  PlanningMonthURL(nextMonth),
		Weeks:    weeks,
	}
}

// buildHourSlots returns a slice of hour label strings from 07:00 to 20:00.
func buildHourSlots() []string {
	slots := make([]string, 14)
	for i := range slots {
		slots[i] = fmt.Sprintf("%02d:00", 7+i)
	}
	return slots
}

// BuildWeekCalendar constructs a PlanningCalendar for the week view.
func BuildWeekCalendar(weekStart time.Time, today time.Time, rows []sqlc.ListUserEventsRangeRow) PlanningCalendar {
	// Index events by date
	byDate := map[string][]sqlc.ListUserEventsRangeRow{}
	for _, row := range rows {
		if !row.EventDate.Valid {
			continue
		}
		key := row.EventDate.Time.Format("2006-01-02")
		byDate[key] = append(byDate[key], row)
	}

	days := make([]CalendarDayColumn, 7)
	for i := 0; i < 7; i++ {
		d := weekStart.AddDate(0, 0, i)
		key := d.Format("2006-01-02")
		events := make([]CalendarTimeEvent, 0, len(byDate[key]))
		for _, row := range byDate[key] {
			events = append(events, calTimeEventFromRow(row))
		}
		days[i] = CalendarDayColumn{
			Date:    d,
			Label:   d.Format("Mon 2"),
			IsToday: samePlanningDay(d, today),
			Events:  events,
		}
	}

	weekEnd := weekStart.AddDate(0, 0, 6)
	var label string
	if weekStart.Month() == weekEnd.Month() {
		label = fmt.Sprintf("%d–%d %s %d", weekStart.Day(), weekEnd.Day(), weekStart.Format("January"), weekStart.Year())
	} else {
		label = weekStart.Format("2 Jan") + " – " + weekEnd.Format("2 Jan 2006")
	}

	prevWeek := weekStart.AddDate(0, 0, -7)
	nextWeek := weekStart.AddDate(0, 0, 7)

	mini, miniLabel := buildMiniMonth(weekStart, today, weekStart, weekEnd)

	return PlanningCalendar{
		View:           "week",
		Label:          label,
		PrevURL:        PlanningWeekURL(prevWeek),
		TodayURL:       PlanningWeekURL(today),
		NextURL:        PlanningWeekURL(nextWeek),
		Days:           days,
		HourSlots:      buildHourSlots(),
		MiniMonth:      mini,
		MiniMonthLabel: miniLabel,
	}
}

// BuildDayCalendar constructs a PlanningCalendar for the day view.
func BuildDayCalendar(date time.Time, today time.Time, rows []sqlc.ListUserEventsRangeRow) PlanningCalendar {
	events := make([]CalendarTimeEvent, 0, len(rows))
	for _, row := range rows {
		events = append(events, calTimeEventFromRow(row))
	}

	days := []CalendarDayColumn{
		{
			Date:    date,
			Label:   date.Format("Monday 2 January"),
			IsToday: samePlanningDay(date, today),
			Events:  events,
		},
	}

	prevDay := date.AddDate(0, 0, -1)
	nextDay := date.AddDate(0, 0, 1)

	mini, miniLabel := buildMiniMonth(date, today, date, date)

	return PlanningCalendar{
		View:           "day",
		Label:          date.Format("Monday 2 January 2006"),
		PrevURL:        PlanningDayURL(prevDay),
		TodayURL:       PlanningDayURL(today),
		NextURL:        PlanningDayURL(nextDay),
		Days:           days,
		HourSlots:      buildHourSlots(),
		MiniMonth:      mini,
		MiniMonthLabel: miniLabel,
	}
}

// buildMiniMonth builds the 7-col mini-month grid for the week/day sidebar panel.
// highlightStart/highlightEnd define the range of days that get the in-week class.
func buildMiniMonth(anchor time.Time, today time.Time, highlightStart time.Time, highlightEnd time.Time) ([][]MiniCalDay, string) {
	first := time.Date(anchor.Year(), anchor.Month(), 1, 0, 0, 0, 0, anchor.Location())
	last := first.AddDate(0, 1, -1)
	gridStart := MondayOf(first)

	var weeks [][]MiniCalDay
	cursor := gridStart
	for w := 0; w < 6; w++ {
		var week []MiniCalDay
		for d := 0; d < 7; d++ {
			inWeek := !cursor.Before(highlightStart) && !cursor.After(highlightEnd)
			week = append(week, MiniCalDay{
				Date:    cursor,
				DayNum:  cursor.Day(),
				IsToday: samePlanningDay(cursor, today),
				InWeek:  inWeek,
				URL:     PlanningDayURL(cursor),
			})
			cursor = cursor.AddDate(0, 0, 1)
		}
		weeks = append(weeks, week)
		if cursor.After(last) && len(weeks) >= 4 {
			break
		}
	}
	return weeks, first.Format("January 2006")
}
