package errortracking

import (
	"context"
	"fmt"
	"time"

	"github.com/getsentry/sentry-go"
)

// SentryProvider implements the Provider interface using Sentry
type SentryProvider struct {
	hub *sentry.Hub
}

// SentryConfig holds the configuration for Sentry
type SentryConfig struct {
	DSN              string
	Environment      string
	Release          string
	Debug            bool
	SampleRate       float64
	TracesSampleRate float64
}

// NewSentryProvider creates a new Sentry provider
func NewSentryProvider(config SentryConfig) (*SentryProvider, error) {
	err := sentry.Init(sentry.ClientOptions{
		Dsn:              config.DSN,
		Environment:      config.Environment,
		Release:          config.Release,
		Debug:            config.Debug,
		AttachStacktrace: true,
		SampleRate:       config.SampleRate,
		TracesSampleRate: config.TracesSampleRate,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to initialize Sentry: %w", err)
	}

	return &SentryProvider{
		hub: sentry.CurrentHub(),
	}, nil
}

// CaptureError captures an error with the given severity and additional context
func (s *SentryProvider) CaptureError(ctx context.Context, err error, severity Severity, extra map[string]interface{}) {
	if err == nil {
		return
	}

	hub := sentry.GetHubFromContext(ctx)
	if hub == nil {
		hub = s.hub
	}

	event := sentry.NewEvent()
	event.Level = s.convertSeverity(severity)
	event.Message = err.Error()
	event.Exception = []sentry.Exception{
		{
			Value:      err.Error(),
			Type:       fmt.Sprintf("%T", err),
			Stacktrace: sentry.ExtractStacktrace(err),
		},
	}

	if extra != nil {
		event.Contexts["extra"] = sentry.Context(extra)
	}

	hub.CaptureEvent(event)
}

// CaptureMessage captures a message with the given severity and additional context
func (s *SentryProvider) CaptureMessage(ctx context.Context, message string, severity Severity, extra map[string]interface{}) {
	if message == "" {
		return
	}

	hub := sentry.GetHubFromContext(ctx)
	if hub == nil {
		hub = s.hub
	}

	event := sentry.NewEvent()
	event.Level = s.convertSeverity(severity)
	event.Message = message

	if extra != nil {
		event.Contexts["extra"] = sentry.Context(extra)
	}

	hub.CaptureEvent(event)
}

// CapturePanic captures a panic with stack trace
func (s *SentryProvider) CapturePanic(ctx context.Context, recovered interface{}, stackTrace []byte, extra map[string]interface{}) {
	if recovered == nil {
		return
	}

	hub := sentry.GetHubFromContext(ctx)
	if hub == nil {
		hub = s.hub
	}

	event := sentry.NewEvent()
	event.Level = sentry.LevelError
	event.Message = fmt.Sprintf("Panic: %v", recovered)
	event.Exception = []sentry.Exception{
		{
			Value: fmt.Sprintf("%v", recovered),
			Type:  "panic",
		},
	}

	extraCtx := sentry.Context{}
	for k, v := range extra {
		extraCtx[k] = v
	}
	if stackTrace != nil {
		extraCtx["stack_trace"] = string(stackTrace)
	}
	if len(extraCtx) > 0 {
		event.Contexts["extra"] = extraCtx
	}

	hub.CaptureEvent(event)
}

// Flush waits for all events to be sent (useful for graceful shutdown)
func (s *SentryProvider) Flush(timeout int) bool {
	return sentry.Flush(time.Duration(timeout) * time.Second)
}

// Close closes the provider and releases resources
func (s *SentryProvider) Close() error {
	sentry.Flush(2 * time.Second)
	return nil
}

// convertSeverity converts our Severity to Sentry's Level
func (s *SentryProvider) convertSeverity(severity Severity) sentry.Level {
	switch severity {
	case SeverityError:
		return sentry.LevelError
	case SeverityWarning:
		return sentry.LevelWarning
	case SeverityInfo:
		return sentry.LevelInfo
	case SeverityDebug:
		return sentry.LevelDebug
	default:
		return sentry.LevelError
	}
}
