package telegram

import (
	"fmt"
	"strings"
	"time"

	"git3.techno-world.net/lrosales/broad-announce/internal/alert"
)

// Command is a parsed bot command. Exactly one of the fields
// is non-nil per command. The handler dispatches on which
// one is set.
type Command struct {
	Raw   string
	Start *StartCmd
	Subscribe *SubscribeCmd
	Unsubscribe *UnsubscribeCmd
	Preferences bool
	Status *StatusCmd
	Mute *MuteCmd
	Unmute bool
	Unknown string
}

// StartCmd is /start <invite_code>. Empty invite_code is OK
// at parse time; the handler returns a friendly "please pass
// your invite code" reply.
type StartCmd struct {
	InviteCode string
}

// SubscribeCmd is /subscribe <source_id> [<min_severity>].
type SubscribeCmd struct {
	SourceID    string
	MinSeverity string
}

// UnsubscribeCmd is /unsubscribe <source_id>.
type UnsubscribeCmd struct {
	SourceID string
}

// StatusCmd is /status [N] with N defaulting to 5.
type StatusCmd struct {
	Limit int
}

// MuteCmd is /mute <duration>. We accept:
//   /mute 2h
//   /mute 30m
//   /mute 90s
//   /mute until 18:00       (HH:MM today, tomorrow if past)
type MuteCmd struct {
	Duration time.Duration // for /mute 2h
	Until    time.Time     // for /mute until HH:MM
}

// Parse turns a raw message text into a Command. The
// message MUST start with '/'. Text without a leading slash
// is returned as a Command{Unknown: <text>}; the handler
// treats Unknown as "ignore, don't reply".
func Parse(text string) Command {
	out := Command{Raw: text}
	t := strings.TrimSpace(text)
	if t == "" {
		out.Unknown = ""
		return out
	}
	if !strings.HasPrefix(t, "/") {
		out.Unknown = t
		return out
	}
	// Strip the leading '/' and split into parts. Telegram
	// commands are space-separated and may include @<bot>
	// suffix (e.g. /start@acme_x_bot).
	parts := strings.Fields(strings.TrimPrefix(t, "/"))
	if len(parts) == 0 {
		out.Unknown = t
		return out
	}
	cmd := parts[0]
	if at := strings.Index(cmd, "@"); at >= 0 {
		cmd = cmd[:at]
	}
	rest := parts[1:]

	switch strings.ToLower(cmd) {
	case "start":
		c := StartCmd{}
		if len(rest) >= 1 {
			c.InviteCode = rest[0]
		}
		out.Start = &c
	case "subscribe":
		if len(rest) < 1 {
			out.Unknown = "subscribe requires <source_id> [<min_severity>]"
			return out
		}
		c := SubscribeCmd{SourceID: rest[0]}
		if len(rest) >= 2 {
			c.MinSeverity = rest[1]
		} else {
			c.MinSeverity = "info" // default
		}
		if _, ok := alert.ValidSeverities[alert.Severity(c.MinSeverity)]; !ok {
			out.Unknown = fmt.Sprintf("min_severity must be one of info|warning|critical|inminent_colapse, got %q", c.MinSeverity)
			return out
		}
		out.Subscribe = &c
	case "unsubscribe":
		if len(rest) < 1 {
			out.Unknown = "unsubscribe requires <source_id>"
			return out
		}
		out.Unsubscribe = &UnsubscribeCmd{SourceID: rest[0]}
	case "preferences":
		out.Preferences = true
	case "status":
		c := StatusCmd{Limit: 5}
		if len(rest) >= 1 {
			var n int
			if _, err := fmt.Sscanf(rest[0], "%d", &n); err == nil && n > 0 && n <= 50 {
				c.Limit = n
			}
		}
		out.Status = &c
	case "mute":
		if len(rest) < 1 {
			out.Unknown = "mute requires <duration> e.g. 2h, 30m, 90s, or 'until 18:00'"
			return out
		}
		if rest[0] == "until" && len(rest) >= 2 {
			now := time.Now().UTC()
			t, err := time.ParseInLocation("15:04", rest[1], time.UTC)
			if err != nil {
				out.Unknown = fmt.Sprintf("can't parse time %q (expected HH:MM): %v", rest[1], err)
				return out
			}
			until := time.Date(now.Year(), now.Month(), now.Day(), t.Hour(), t.Minute(), 0, 0, time.UTC)
			if until.Before(now) {
				until = until.Add(24 * time.Hour) // tomorrow
			}
			out.Mute = &MuteCmd{Until: until}
			return out
		}
		d, err := time.ParseDuration(rest[0])
		if err != nil {
			out.Unknown = fmt.Sprintf("can't parse duration %q (e.g. 2h, 30m, 90s): %v", rest[0], err)
			return out
		}
		out.Mute = &MuteCmd{Duration: d}
	case "unmute":
		out.Unmute = true
	default:
		out.Unknown = "unknown command: /" + cmd
	}
	return out
}
