package fiber

import (
	"slices"

	"github.com/gofiber/fiber/v3/log"
)

type (
	// OnRouteHandler defines the hook signature invoked whenever a route is registered.
	OnRouteHandler = func(Route) error
	// OnNameHandler shares the OnRouteHandler signature for route naming callbacks.
	OnNameHandler = OnRouteHandler
	// OnGroupHandler defines the hook signature invoked whenever a group is registered.
	OnGroupHandler = func(Group) error
	// OnGroupNameHandler shares the OnGroupHandler signature for group naming callbacks.
	OnGroupNameHandler = OnGroupHandler
	// OnListenHandler runs when the application begins listening and receives the listener details.
	OnListenHandler = func(ListenData) error
	// OnPreStartupMessageHandler runs before Fiber prints the startup banner.
	OnPreStartupMessageHandler = func(*PreStartupMessageData) error
	// OnPostStartupMessageHandler runs after Fiber prints (or skips) the startup banner.
	OnPostStartupMessageHandler = func(*PostStartupMessageData) error
	// OnPreShutdownHandler runs before the application shuts down.
	OnPreShutdownHandler = func() error
	// OnPostShutdownHandler runs after shutdown and receives the shutdown result.
	OnPostShutdownHandler = func(error) error
	// OnForkHandler runs inside a forked worker process and receives the worker ID.
	OnForkHandler = func(int) error
	// OnMountHandler runs after a sub-application mounts to a parent and receives the parent app reference.
	OnMountHandler = func(*App) error
)

// Hooks is a struct to use it with App.
type Hooks struct {
	// Embed app
	app *App

	// Hooks
	onRoute        []OnRouteHandler
	onName         []OnNameHandler
	onGroup        []OnGroupHandler
	onGroupName    []OnGroupNameHandler
	onListen       []OnListenHandler
	onPreStartup   []OnPreStartupMessageHandler
	onPostStartup  []OnPostStartupMessageHandler
	onPreShutdown  []OnPreShutdownHandler
	onPostShutdown []OnPostShutdownHandler
	onFork         []OnForkHandler
	onMount        []OnMountHandler
}

type StartupMessageLevel int

const (
	// StartupMessageLevelInfo represents informational startup message entries.
	StartupMessageLevelInfo StartupMessageLevel = iota
	// StartupMessageLevelWarning represents warning startup message entries.
	StartupMessageLevelWarning
	// StartupMessageLevelError represents error startup message entries.
	StartupMessageLevelError
)

const errString = "ERROR"

// startupMessageEntry represents a single line of startup message information.
type startupMessageEntry struct {
	key      string
	title    string
	value    string
	priority int
	level    StartupMessageLevel
}

// ListenData contains the listener metadata provided to OnListenHandler.
type ListenData struct {
	ColorScheme Colors
	Host        string
	Port        string
	Version     string
	AppName     string

	ChildPIDs []int

	HandlerCount int
	ProcessCount int
	PID          int

	TLS     bool
	Prefork bool
}

// PreStartupMessageData contains metadata exposed to OnPreStartupMessage hooks.
type PreStartupMessageData struct {
	*ListenData

	// BannerHeader allows overriding the ASCII art banner displayed at startup.
	BannerHeader string

	entries []startupMessageEntry

	// PreventDefault, when set to true, suppresses the default startup message.
	PreventDefault bool
}

// AddInfo adds an informational entry to the startup message with "INFO" label.
func (sm *PreStartupMessageData) AddInfo(key, title, value string, priority ...int) {
	pri := -1
	if len(priority) > 0 {
		pri = priority[0]
	}

	sm.addEntry(key, title, value, pri, StartupMessageLevelInfo)
}

// AddWarning adds a warning entry to the startup message with "WARNING" label.
func (sm *PreStartupMessageData) AddWarning(key, title, value string, priority ...int) {
	pri := -1
	if len(priority) > 0 {
		pri = priority[0]
	}

	sm.addEntry(key, title, value, pri, StartupMessageLevelWarning)
}

// AddError adds an error entry to the startup message with "ERROR" label.
func (sm *PreStartupMessageData) AddError(key, title, value string, priority ...int) {
	pri := -1
	if len(priority) > 0 {
		pri = priority[0]
	}

	sm.addEntry(key, title, value, pri, StartupMessageLevelError)
}

// EntryKeys returns all entry keys currently present in the startup message.
func (sm *PreStartupMessageData) EntryKeys() []string {
	keys := make([]string, 0, len(sm.entries))
	for _, entry := range sm.entries {
		keys = append(keys, entry.key)
	}
	return keys
}

// ResetEntries removes all existing entries from the startup message.
func (sm *PreStartupMessageData) ResetEntries() {
	sm.entries = sm.entries[:0]
}

// DeleteEntry removes a specific entry from the startup message by its key.
func (sm *PreStartupMessageData) DeleteEntry(key string) {
	if sm.entries == nil {
		return
	}

	for i, entry := range sm.entries {
		if entry.key == key {
			sm.entries = append(sm.entries[:i], sm.entries[i+1:]...)
			return
		}
	}
}

func (sm *PreStartupMessageData) addEntry(key, title, value string, priority int, level StartupMessageLevel) {
	if sm.entries == nil {
		sm.entries = make([]startupMessageEntry, 0, 8)
	}

	for i, entry := range sm.entries {
		if entry.key != key {
			continue
		}

		sm.entries[i].value = value
		sm.entries[i].title = title
		sm.entries[i].level = level
		sm.entries[i].priority = priority
		return
	}

	sm.entries = append(sm.entries, startupMessageEntry{
		key:      key,
		title:    title,
		value:    value,
		priority: priority,
		level:    level,
	})
}

func newPreStartupMessageData(listenData *ListenData) *PreStartupMessageData {
	return &PreStartupMessageData{ListenData: listenData}
}

// PostStartupMessageData contains metadata exposed to OnPostStartupMessage hooks.
type PostStartupMessageData struct {
	*ListenData

	// Disabled indicates whether the startup message was disabled via configuration.
	Disabled bool

	// IsChild indicates whether the current process is a child in prefork mode.
	IsChild bool

	// Prevented indicates whether the startup message was suppressed by a pre-startup hook using PreventDefault property.
	Prevented bool
}

func newPostStartupMessageData(listenData *ListenData, disabled, isChild, prevented bool) *PostStartupMessageData {
	clone := *listenData
	if len(listenData.ChildPIDs) > 0 {
		clone.ChildPIDs = slices.Clone(listenData.ChildPIDs)
	}

	return &PostStartupMessageData{
		ListenData: &clone,
		Disabled:   disabled,
		IsChild:    isChild,
		Prevented:  prevented,
	}
}

func newHooks(app *App) *Hooks {
	return &Hooks{
		app:            app,
		onRoute:        make([]OnRouteHandler, 0),
		onGroup:        make([]OnGroupHandler, 0),
		onGroupName:    make([]OnGroupNameHandler, 0),
		onName:         make([]OnNameHandler, 0),
		onListen:       make([]OnListenHandler, 0),
		onPreStartup:   make([]OnPreStartupMessageHandler, 0),
		onPostStartup:  make([]OnPostStartupMessageHandler, 0),
		onPreShutdown:  make([]OnPreShutdownHandler, 0),
		onPostShutdown: make([]OnPostShutdownHandler, 0),
		onFork:         make([]OnForkHandler, 0),
		onMount:        make([]OnMountHandler, 0),
	}
}

// OnRoute is a hook to execute user functions on each route registration.
// Also you can get route properties by route parameter.
func (h *Hooks) OnRoute(handler ...OnRouteHandler) {
	h.app.mutex.Lock()
	h.onRoute = append(h.onRoute, handler...)
	h.app.mutex.Unlock()
}

// OnName is a hook to execute user functions on each route naming.
// Also you can get route properties by route parameter.
//
// WARN: OnName only works with naming routes, not groups.
func (h *Hooks) OnName(handler ...OnNameHandler) {
	h.app.mutex.Lock()
	h.onName = append(h.onName, handler...)
	h.app.mutex.Unlock()
}

// OnGroup is a hook to execute user functions on each group registration.
// Also you can get group properties by group parameter.
func (h *Hooks) OnGroup(handler ...OnGroupHandler) {
	h.app.mutex.Lock()
	h.onGroup = append(h.onGroup, handler...)
	h.app.mutex.Unlock()
}

// OnGroupName is a hook to execute user functions on each group naming.
// Also you can get group properties by group parameter.
//
// WARN: OnGroupName only works with naming groups, not routes.
func (h *Hooks) OnGroupName(handler ...OnGroupNameHandler) {
	h.app.mutex.Lock()
	h.onGroupName = append(h.onGroupName, handler...)
	h.app.mutex.Unlock()
}

// OnListen is a hook to execute user functions on Listen or Listener.
func (h *Hooks) OnListen(handler ...OnListenHandler) {
	h.app.mutex.Lock()
	h.onListen = append(h.onListen, handler...)
	h.app.mutex.Unlock()
}

// OnPreStartupMessage is a hook to execute user functions before the startup message is printed.
func (h *Hooks) OnPreStartupMessage(handler ...OnPreStartupMessageHandler) {
	h.app.mutex.Lock()
	h.onPreStartup = append(h.onPreStartup, handler...)
	h.app.mutex.Unlock()
}

// OnPostStartupMessage is a hook to execute user functions after the startup message is printed (or skipped).
func (h *Hooks) OnPostStartupMessage(handler ...OnPostStartupMessageHandler) {
	h.app.mutex.Lock()
	h.onPostStartup = append(h.onPostStartup, handler...)
	h.app.mutex.Unlock()
}

// OnPreShutdown is a hook to execute user functions before Shutdown.
func (h *Hooks) OnPreShutdown(handler ...OnPreShutdownHandler) {
	h.app.mutex.Lock()
	h.onPreShutdown = append(h.onPreShutdown, handler...)
	h.app.mutex.Unlock()
}

// OnPostShutdown is a hook to execute user functions after Shutdown.
func (h *Hooks) OnPostShutdown(handler ...OnPostShutdownHandler) {
	h.app.mutex.Lock()
	h.onPostShutdown = append(h.onPostShutdown, handler...)
	h.app.mutex.Unlock()
}

// OnFork is a hook to execute user function after fork process.
func (h *Hooks) OnFork(handler ...OnForkHandler) {
	h.app.mutex.Lock()
	h.onFork = append(h.onFork, handler...)
	h.app.mutex.Unlock()
}

// OnMount is a hook to execute user function after mounting process.
// The mount event is fired when sub-app is mounted on a parent app. The parent app is passed as a parameter.
// It works for app and group mounting.
func (h *Hooks) OnMount(handler ...OnMountHandler) {
	h.app.mutex.Lock()
	h.onMount = append(h.onMount, handler...)
	h.app.mutex.Unlock()
}

func (h *Hooks) executeOnRouteHooks(route *Route) error {
	if route == nil {
		return nil
	}

	cloned := *route

	// Check mounting
	if h.app.mountFields.mountPath != "" {
		cloned.path = h.app.mountFields.mountPath + cloned.path
		cloned.Path = cloned.path
	}

	for _, v := range h.onRoute {
		if err := v(cloned); err != nil {
			return err
		}
	}

	return nil
}

func (h *Hooks) executeOnNameHooks(route *Route) error {
	if route == nil {
		return nil
	}

	cloned := *route

	// Check mounting
	if h.app.mountFields.mountPath != "" {
		cloned.path = h.app.mountFields.mountPath + cloned.path
		cloned.Path = cloned.path
	}

	for _, v := range h.onName {
		if err := v(cloned); err != nil {
			return err
		}
	}

	return nil
}

func (h *Hooks) executeOnGroupHooks(group Group) error {
	// Check mounting
	if h.app.mountFields.mountPath != "" {
		group.Prefix = h.app.mountFields.mountPath + group.Prefix
	}

	for _, v := range h.onGroup {
		if err := v(group); err != nil {
			return err
		}
	}

	return nil
}

func (h *Hooks) executeOnGroupNameHooks(group Group) error {
	// Check mounting
	if h.app.mountFields.mountPath != "" {
		group.Prefix = h.app.mountFields.mountPath + group.Prefix
	}

	for _, v := range h.onGroupName {
		if err := v(group); err != nil {
			return err
		}
	}

	return nil
}

func (h *Hooks) executeOnListenHooks(listenData *ListenData) error {
	for _, v := range h.onListen {
		if err := v(*listenData); err != nil {
			return err
		}
	}

	return nil
}

func (h *Hooks) executeOnPreStartupMessageHooks(data *PreStartupMessageData) error {
	for _, handler := range h.onPreStartup {
		if err := handler(data); err != nil {
			return err
		}
	}

	return nil
}

func (h *Hooks) executeOnPostStartupMessageHooks(data *PostStartupMessageData) error {
	for _, handler := range h.onPostStartup {
		if err := handler(data); err != nil {
			return err
		}
	}

	return nil
}

func (h *Hooks) executeOnPreShutdownHooks() {
	for _, v := range h.onPreShutdown {
		if err := v(); err != nil {
			log.Errorf("failed to call pre shutdown hook: %v", err)
		}
	}
}

func (h *Hooks) executeOnPostShutdownHooks(err error) {
	for _, v := range h.onPostShutdown {
		if hookErr := v(err); hookErr != nil {
			log.Errorf("failed to call post shutdown hook: %v", hookErr)
		}
	}
}

func (h *Hooks) executeOnForkHooks(pid int) {
	for _, v := range h.onFork {
		if err := v(pid); err != nil {
			log.Errorf("failed to call fork hook: %v", err)
		}
	}
}

func (h *Hooks) executeOnMountHooks(app *App) error {
	for _, v := range h.onMount {
		if err := v(app); err != nil {
			return err
		}
	}

	return nil
}
