package context

import (
	"context"
	"fmt"
	"time"

	apperrors "github.com/darksworm/argonaut/pkg/errors"
)

// TimeoutConfig holds timeout configuration for different operations
type TimeoutConfig struct {
	Default  time.Duration
	API      time.Duration
	Stream   time.Duration
	Auth     time.Duration
	Sync     time.Duration
	Resource time.Duration
	UI       time.Duration
}

// DefaultTimeouts provides sensible defaults for different operation types
var DefaultTimeouts = TimeoutConfig{
	Default:  5 * time.Second,  // Most operations should be fast
	API:      10 * time.Second, // API calls - increased to handle slow TLS handshakes
	Stream:   0,                // No timeout for streams (handled by parent context)
	Auth:     10 * time.Second, // Auth - increased to handle slow TLS handshakes
	Sync:     10 * time.Second, // Sync operations - max 10 seconds
	Resource: 10 * time.Second, // Resource queries - increased to handle slow TLS
	UI:       2 * time.Second,  // UI operations must be very fast
}

// OperationType represents different types of operations that need timeouts
type OperationType string

const (
	OpAPI      OperationType = "api"
	OpStream   OperationType = "stream"
	OpAuth     OperationType = "auth"
	OpSync     OperationType = "sync"
	OpResource OperationType = "resource"
	OpUI       OperationType = "ui"
)

// timeoutDurationKey is used to store the original timeout duration in the context
// so error handlers can report the configured value (not the remaining time).
type timeoutDurationKey struct{}

// withTimeoutTagged creates a context with the given timeout and stores the original
// duration as a context value for accurate error reporting.
func withTimeoutTagged(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
	ctx, cancel := context.WithTimeout(parent, timeout)
	ctx = context.WithValue(ctx, timeoutDurationKey{}, timeout)
	return ctx, cancel
}

// GetTimeoutDuration returns the original timeout duration stored in the context,
// or (0, false) if none was set via this package's helpers.
func GetTimeoutDuration(ctx context.Context) (time.Duration, bool) {
	d, ok := ctx.Value(timeoutDurationKey{}).(time.Duration)
	return d, ok
}

// WithTimeout creates a context with timeout based on operation type
func WithTimeout(parent context.Context, opType OperationType) (context.Context, context.CancelFunc) {
	timeout := getTimeoutForOperation(opType)
	if timeout == 0 {
		// For operations with no timeout (like streams), return the parent context
		// but still provide a cancel function for cleanup
		ctx, cancel := context.WithCancel(parent)
		return ctx, cancel
	}
	return withTimeoutTagged(parent, timeout)
}

// WithCancel creates a cancellable context
func WithCancel(parent context.Context) (context.Context, context.CancelFunc) {
	return context.WithCancel(parent)
}

// getTimeoutForOperation returns the appropriate timeout for the given operation type
func getTimeoutForOperation(opType OperationType) time.Duration {
	switch opType {
	case OpAPI:
		return DefaultTimeouts.API
	case OpStream:
		return DefaultTimeouts.Stream
	case OpAuth:
		return DefaultTimeouts.Auth
	case OpSync:
		return DefaultTimeouts.Sync
	case OpResource:
		return DefaultTimeouts.Resource
	case OpUI:
		return DefaultTimeouts.UI
	default:
		return DefaultTimeouts.Default
	}
}

// HandleTimeout converts a context timeout error to a structured error
func HandleTimeout(ctx context.Context, opType OperationType) *apperrors.ArgonautError {
	if ctx.Err() == context.DeadlineExceeded {
		return apperrors.TimeoutError(
			"OPERATION_TIMEOUT",
			fmt.Sprintf("Operation timed out after %v", getTimeoutForOperation(opType)),
		).WithDetails(fmt.Sprintf("Operation type: %s", opType))
	}

	if ctx.Err() == context.Canceled {
		return apperrors.New(
			apperrors.ErrorInternal,
			"OPERATION_CANCELED",
			"Operation was canceled",
		).WithDetails(fmt.Sprintf("Operation type: %s", opType))
	}

	return nil
}

// WithTimeoutAndRetry creates a context with timeout and provides retry information
type RetryableContext struct {
	Context    context.Context
	Cancel     context.CancelFunc
	Attempt    int
	MaxRetries int
	OpType     OperationType
}

// Convenience functions for common timeout patterns

// WithAPITimeout creates a context specifically for API operations
func WithAPITimeout(parent context.Context) (context.Context, context.CancelFunc) {
	return WithTimeout(parent, OpAPI)
}

// WithSyncTimeout creates a context specifically for sync operations
func WithSyncTimeout(parent context.Context) (context.Context, context.CancelFunc) {
	return WithTimeout(parent, OpSync)
}

// WithResourceTimeout creates a context specifically for resource operations
func WithResourceTimeout(parent context.Context) (context.Context, context.CancelFunc) {
	return WithTimeout(parent, OpResource)
}

// WithMinAPITimeout creates an API timeout context that is at least minTimeout.
// This is useful for inherently slow operations (diffs, rollbacks) that need a
// guaranteed minimum even when the user has configured a shorter request_timeout.
func WithMinAPITimeout(parent context.Context, minTimeout time.Duration) (context.Context, context.CancelFunc) {
	timeout := DefaultTimeouts.API
	if minTimeout > timeout {
		timeout = minTimeout
	}
	return withTimeoutTagged(parent, timeout)
}

// SetRequestTimeout updates all request-related timeouts to the specified duration.
// This affects API, Auth, Sync, and Resource operations.
// UI and Stream timeouts remain unchanged to maintain UI responsiveness and streaming functionality.
//
// Parameters:
//   - timeout: The duration to set for all request-related operations. Should be positive.
//
// Example:
//   SetRequestTimeout(30 * time.Second) // Sets all request timeouts to 30 seconds
func SetRequestTimeout(timeout time.Duration) {
	DefaultTimeouts.API = timeout
	DefaultTimeouts.Auth = timeout
	DefaultTimeouts.Sync = timeout
	DefaultTimeouts.Resource = timeout
	DefaultTimeouts.Default = timeout
	// Keep UI timeout at 2s for responsiveness
	// Keep Stream timeout at 0 (no timeout)
}
