package database

import (
	"context"
	"time"

	"veza-backend-api/internal/metrics"

	"go.uber.org/zap"
	"gorm.io/gorm"
)

// PerformanceMonitor monitors database query performance and logs slow queries
// BE-DB-018: Add database performance monitoring with slow query logging
type PerformanceMonitor struct {
	logger             *zap.Logger
	slowQueryThreshold time.Duration // Threshold for slow query logging
	enabled            bool
}

// PerformanceMonitorConfig contains configuration for the performance monitor
type PerformanceMonitorConfig struct {
	Logger             *zap.Logger
	SlowQueryThreshold time.Duration // Default: 1 second
	Enabled            bool          // Default: true
}

// NewPerformanceMonitor creates a new performance monitor
func NewPerformanceMonitor(config PerformanceMonitorConfig) *PerformanceMonitor {
	threshold := config.SlowQueryThreshold
	if threshold == 0 {
		threshold = 1 * time.Second // Default: 1 second
	}

	enabled := config.Enabled
	if config.Logger == nil {
		enabled = false // Disable if no logger
	}

	return &PerformanceMonitor{
		logger:             config.Logger,
		slowQueryThreshold: threshold,
		enabled:            enabled,
	}
}

// WrapGORM wraps a GORM DB instance with performance monitoring
func (pm *PerformanceMonitor) WrapGORM(db *gorm.DB) *gorm.DB {
	if !pm.enabled {
		return db
	}

	// Register GORM callback to monitor queries
	db.Callback().Query().Before("gorm:query").Register("performance_monitor:before_query", pm.beforeQuery)
	db.Callback().Query().After("gorm:query").Register("performance_monitor:after_query", pm.afterQuery)
	db.Callback().Create().Before("gorm:create").Register("performance_monitor:before_create", pm.beforeQuery)
	db.Callback().Create().After("gorm:create").Register("performance_monitor:after_create", pm.afterQuery)
	db.Callback().Update().Before("gorm:update").Register("performance_monitor:before_update", pm.beforeQuery)
	db.Callback().Update().After("gorm:update").Register("performance_monitor:after_update", pm.afterQuery)
	db.Callback().Delete().Before("gorm:delete").Register("performance_monitor:before_delete", pm.beforeQuery)
	db.Callback().Delete().After("gorm:delete").Register("performance_monitor:after_delete", pm.afterQuery)

	return db
}

// beforeQuery is called before a query executes
func (pm *PerformanceMonitor) beforeQuery(db *gorm.DB) {
	// Store start time in the statement context
	ctx := context.WithValue(db.Statement.Context, "query_start_time", time.Now())
	db.Statement.Context = ctx
}

// afterQuery is called after a query executes
func (pm *PerformanceMonitor) afterQuery(db *gorm.DB) {
	// Get start time from context
	startTime, ok := db.Statement.Context.Value("query_start_time").(time.Time)
	if !ok {
		return
	}

	duration := time.Since(startTime)

	// Extract operation type and table name
	operation := "UNKNOWN"
	if db.Statement != nil {
		// Try to determine operation from SQL
		sqlStr := db.Statement.SQL.String()
		if len(sqlStr) > 0 {
			upperSQL := sqlStr[:min(10, len(sqlStr))]
			switch {
			case contains(upperSQL, "SELECT"):
				operation = "SELECT"
			case contains(upperSQL, "INSERT"):
				operation = "INSERT"
			case contains(upperSQL, "UPDATE"):
				operation = "UPDATE"
			case contains(upperSQL, "DELETE"):
				operation = "DELETE"
			}
		}

		table := "unknown"
		if db.Statement.Table != "" {
			table = db.Statement.Table
		} else if db.Statement.Model != nil {
			// Try to get table name from model
			if stmt := db.Statement; stmt != nil {
				table = stmt.Schema.Table
			}
		}

		// Record metrics
		metrics.RecordDBQuery(operation, table, duration)

		// Log slow queries
		if duration >= pm.slowQueryThreshold {
			pm.logSlowQuery(operation, table, duration, db)
		}
	}
}

// logSlowQuery logs a slow query with details
func (pm *PerformanceMonitor) logSlowQuery(operation, table string, duration time.Duration, db *gorm.DB) {
	fields := []zap.Field{
		zap.String("operation", operation),
		zap.String("table", table),
		zap.Duration("duration", duration),
		zap.Duration("threshold", pm.slowQueryThreshold),
	}

	// Add SQL if available (be careful with sensitive data)
	if db.Statement != nil {
		sqlStr := db.Statement.SQL.String()
		if len(sqlStr) > 0 {
			// Truncate SQL if too long
			if len(sqlStr) > 500 {
				sqlStr = sqlStr[:500] + "..."
			}
			fields = append(fields, zap.String("sql", sqlStr))
		}
	}

	// Add error if query failed
	if db.Error != nil {
		fields = append(fields, zap.Error(db.Error))
		pm.logger.Warn("Slow query with error", fields...)
	} else {
		pm.logger.Warn("Slow query detected", fields...)
	}

	// Record slow query metric
	metrics.RecordSlowQuery(operation, table, duration)
}

// MeasureQuery measures a query execution time and logs if slow
// This is a helper function for manual query measurement
func (pm *PerformanceMonitor) MeasureQuery(operation, table string, fn func() error) error {
	if !pm.enabled {
		return fn()
	}

	start := time.Now()
	err := fn()
	duration := time.Since(start)

	// Record metrics
	metrics.RecordDBQuery(operation, table, duration)

	// Log if slow
	if duration >= pm.slowQueryThreshold {
		fields := []zap.Field{
			zap.String("operation", operation),
			zap.String("table", table),
			zap.Duration("duration", duration),
			zap.Duration("threshold", pm.slowQueryThreshold),
		}
		if err != nil {
			fields = append(fields, zap.Error(err))
			pm.logger.Warn("Slow query with error", fields...)
		} else {
			pm.logger.Warn("Slow query detected", fields...)
		}

		// Record slow query metric
		metrics.RecordSlowQuery(operation, table, duration)
	}

	return err
}

// Helper functions
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func contains(s, substr string) bool {
	return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && s[:len(substr)] == substr)
}
