package tools

import (
	"fmt"
	"os/exec"
	"runtime"
	"strings"
	"sync"
	"time"

	"gokin/internal/format"
	"gokin/internal/logging"
)

// NotificationManager handles user notifications for tool execution
type NotificationManager struct {
	mu                  sync.RWMutex
	history             []Notification
	maxHistory          int
	onNotify            func(Notification)
	quietMode           bool
	verboseMode         bool
	nativeNotifications bool            // Send native macOS notifications (off by default, enabled via config)
	minNotifyDuration   time.Duration   // Minimum duration for success notifications to trigger callback/native (default 5s)
	silentTools         map[string]bool // Tools that shouldn't notify
}

// Notification represents a user-facing notification
type Notification struct {
	ID        string
	Type      NotificationType
	ToolName  string
	Message   string
	Details   string
	Timestamp time.Time
	Summary   *ExecutionSummary
	Duration  time.Duration // Duration of the operation (0 = unknown)
	Read      bool
}

// NotificationType represents the severity/type of notification
type NotificationType string

const (
	NotificationTypeInfo     NotificationType = "info"
	NotificationTypeSuccess  NotificationType = "success"
	NotificationTypeWarning  NotificationType = "warning"
	NotificationTypeError    NotificationType = "error"
	NotificationTypeProgress NotificationType = "progress"
)

// NewNotificationManager creates a new notification manager
func NewNotificationManager() *NotificationManager {
	return &NotificationManager{
		history:             make([]Notification, 0),
		maxHistory:          100,
		nativeNotifications: false,
		minNotifyDuration:   5 * time.Second,
		silentTools:         make(map[string]bool),
	}
}

// SetOnNotify sets the callback function for notifications
func (nm *NotificationManager) SetOnNotify(fn func(Notification)) {
	nm.mu.Lock()
	defer nm.mu.Unlock()
	nm.onNotify = fn
}

// EnableQuietMode suppresses non-error notifications
func (nm *NotificationManager) EnableQuietMode(enabled bool) {
	nm.mu.Lock()
	defer nm.mu.Unlock()
	nm.quietMode = enabled
}

// EnableVerboseMode shows all notifications including progress
func (nm *NotificationManager) EnableVerboseMode(enabled bool) {
	nm.mu.Lock()
	defer nm.mu.Unlock()
	nm.verboseMode = enabled
}

// EnableNativeNotifications enables/disables native macOS Notification Center alerts.
// Enabled by default; can be toggled off via config.
func (nm *NotificationManager) EnableNativeNotifications(enabled bool) {
	nm.mu.Lock()
	defer nm.mu.Unlock()
	nm.nativeNotifications = enabled
}

// SetSilentTool configures a tool to not send notifications
func (nm *NotificationManager) SetSilentTool(toolName string, silent bool) {
	nm.mu.Lock()
	defer nm.mu.Unlock()
	if silent {
		nm.silentTools[toolName] = true
	} else {
		delete(nm.silentTools, toolName)
	}
}

// Notify sends a notification to the user
func (nm *NotificationManager) Notify(typ NotificationType, toolName, message, details string, summary *ExecutionSummary, duration time.Duration) {
	nm.mu.Lock()
	defer nm.mu.Unlock()

	// Check if tool is silent
	if nm.silentTools[toolName] {
		return
	}

	// Filter based on mode
	if nm.quietMode && typ != NotificationTypeError && typ != NotificationTypeWarning {
		return
	}
	if !nm.verboseMode && typ == NotificationTypeProgress {
		return
	}

	notif := Notification{
		ID:        fmt.Sprintf("%s-%d", toolName, time.Now().UnixNano()),
		Type:      typ,
		ToolName:  toolName,
		Message:   message,
		Details:   details,
		Timestamp: time.Now(),
		Summary:   summary,
		Duration:  duration,
		Read:      false,
	}

	// Always add to history for audit/stats
	nm.history = append(nm.history, notif)
	if len(nm.history) > nm.maxHistory {
		nm.history = nm.history[1:]
	}

	// Suppress callback/native for fast success operations
	if typ == NotificationTypeSuccess && duration > 0 && duration < nm.minNotifyDuration {
		return
	}

	// Call callback if set. Wrapped in defer-recover because the callback
	// is external code (set by App.SetNotifyCallback) and a panic there
	// would crash the whole process. Same pattern as Manager.monitorTask
	// in v0.78.18.
	if nm.onNotify != nil {
		go func() {
			defer func() {
				if r := recover(); r != nil {
					logging.Error("notification onNotify callback panicked",
						"panic", r,
						"stack", logging.PanicStack())
				}
			}()
			nm.onNotify(notif)
		}()
	}

	// Native macOS notifications (opt-in via config). sendNativeNotification
	// is internal but exec.Command + AppleScript could plausibly fail in
	// surprising ways (security policy, malformed escaping) — wrap for
	// uniformity.
	if runtime.GOOS == "darwin" && nm.nativeNotifications {
		go func() {
			defer func() {
				if r := recover(); r != nil {
					logging.Error("native notification panicked",
						"panic", r,
						"stack", logging.PanicStack())
				}
			}()
			nm.sendNativeNotification(notif)
		}()
	}
}

// sendNativeNotification sends a native macOS notification using AppleScript.
// Filtering: Error/Warning always shown; Success only if Duration >= 10s; Info/Progress suppressed.
func (nm *NotificationManager) sendNativeNotification(n Notification) {
	switch n.Type {
	case NotificationTypeError, NotificationTypeWarning:
		// Always show
	case NotificationTypeSuccess:
		// Only show for long-running operations (10s+)
		if n.Duration > 0 && n.Duration < 10*time.Second {
			return
		}
	default:
		// Info/Progress — suppress native notifications
		return
	}

	title := "Gokin"
	if n.ToolName != "" {
		title = fmt.Sprintf("Gokin: %s", n.ToolName)
	}

	// Sanitize both msg and title for AppleScript string interpolation.
	sanitize := func(s string) string {
		s = strings.ReplaceAll(s, "\\", "\\\\")
		s = strings.ReplaceAll(s, "\"", "\\\"")
		s = strings.ReplaceAll(s, "\n", " ")
		s = strings.ReplaceAll(s, "\r", "")
		return s
	}
	msg := sanitize(n.Message)
	title = sanitize(title)

	script := fmt.Sprintf("display notification \"%s\" with title \"%s\"", msg, title)
	_ = exec.Command("osascript", "-e", script).Run()
}

// NotifyInfo sends an info notification
func (nm *NotificationManager) NotifyInfo(toolName, message string) {
	nm.Notify(NotificationTypeInfo, toolName, message, "", nil, 0)
}

// NotifySuccess sends a success notification
func (nm *NotificationManager) NotifySuccess(toolName, message string, summary *ExecutionSummary, duration time.Duration) {
	details := fmt.Sprintf("Completed in %s", format.Duration(duration))
	nm.Notify(NotificationTypeSuccess, toolName, message, details, summary, duration)
}

// NotifyWarning sends a warning notification
func (nm *NotificationManager) NotifyWarning(toolName, message string, details []string) {
	detailStr := ""
	if len(details) > 0 {
		detailStr = fmt.Sprintf("Warnings:\n• %s", joinStrings(details, "\n• "))
	}
	nm.Notify(NotificationTypeWarning, toolName, message, detailStr, nil, 0)
}

// NotifyError sends an error notification
func (nm *NotificationManager) NotifyError(toolName, message, error string) {
	nm.Notify(NotificationTypeError, toolName, message, error, nil, 0)
}

// NotifyProgress sends a progress notification
func (nm *NotificationManager) NotifyProgress(toolName string, elapsed time.Duration) {
	message := fmt.Sprintf("Running... (%s)", FormatDuration(elapsed))
	nm.Notify(NotificationTypeProgress, toolName, message, "", nil, 0)
}

// NotifyValidation sends validation warnings
func (nm *NotificationManager) NotifyValidation(toolName string, check *PreFlightCheck) {
	if check == nil || len(check.Warnings) == 0 {
		return
	}
	nm.NotifyWarning(toolName, "Safety validation warnings", check.Warnings)
}

// NotifyDenied sends a permission denied notification
func (nm *NotificationManager) NotifyDenied(toolName, reason string) {
	nm.Notify(NotificationTypeError, toolName, "Permission denied", reason, nil, 0)
}

// NotifyApproved sends a permission approved notification
func (nm *NotificationManager) NotifyApproved(toolName string, summary *ExecutionSummary) {
	message := fmt.Sprintf("Approved: %s", summary.DisplayName)
	nm.Notify(NotificationTypeInfo, toolName, message, "", summary, 0)
}

// GetHistory returns notification history
func (nm *NotificationManager) GetHistory(limit int) []Notification {
	nm.mu.RLock()
	defer nm.mu.RUnlock()

	if limit <= 0 || limit > len(nm.history) {
		limit = len(nm.history)
	}

	// Return most recent first
	result := make([]Notification, limit)
	start := len(nm.history) - limit
	copy(result, nm.history[start:])
	return result
}

// GetUnreadCount returns the number of unread notifications
func (nm *NotificationManager) GetUnreadCount() int {
	nm.mu.RLock()
	defer nm.mu.RUnlock()

	count := 0
	for _, n := range nm.history {
		if !n.Read {
			count++
		}
	}
	return count
}

// MarkAsRead marks a notification as read
func (nm *NotificationManager) MarkAsRead(id string) {
	nm.mu.Lock()
	defer nm.mu.Unlock()

	for i := range nm.history {
		if nm.history[i].ID == id {
			nm.history[i].Read = true
			break
		}
	}
}

// MarkAllAsRead marks all notifications as read
func (nm *NotificationManager) MarkAllAsRead() {
	nm.mu.Lock()
	defer nm.mu.Unlock()

	for i := range nm.history {
		nm.history[i].Read = true
	}
}

// ClearHistory clears all notification history
func (nm *NotificationManager) ClearHistory() {
	nm.mu.Lock()
	defer nm.mu.Unlock()
	nm.history = make([]Notification, 0)
}

// GetStats returns notification statistics
func (nm *NotificationManager) GetStats() NotificationStats {
	nm.mu.RLock()
	defer nm.mu.RUnlock()

	stats := NotificationStats{
		Total:  len(nm.history),
		Unread: 0,
		ByType: make(map[NotificationType]int),
		ByTool: make(map[string]int),
	}

	for _, n := range nm.history {
		if !n.Read {
			stats.Unread++
		}
		stats.ByType[n.Type]++
		stats.ByTool[n.ToolName]++
	}

	return stats
}

// NotificationStats holds notification statistics
type NotificationStats struct {
	Total  int
	Unread int
	ByType map[NotificationType]int
	ByTool map[string]int
}

// FormatDuration converts duration to human-readable string
func FormatDuration(d time.Duration) string {
	if d < time.Millisecond {
		return "< 1ms"
	}
	if d < time.Second {
		return d.Round(time.Millisecond).String()
	}
	if d < time.Minute {
		return d.Round(time.Second).String()
	}
	return d.Round(time.Second).String()
}

func joinStrings(strs []string, sep string) string {
	// Was a hand-rolled `result += sep + strs[i]` loop — O(n²) for large
	// slices and just a re-implementation of strings.Join. Use the
	// stdlib version which uses a Builder internally.
	return strings.Join(strs, sep)
}
