package user

import (
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"time"

	"dance-lessons-coach/pkg/config"

	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// SQLiteRepository implements UserRepository using SQLite
type SQLiteRepository struct {
	db         *gorm.DB
	dbPath     string
	config     *config.Config
	spanPrefix string
}

// NewSQLiteRepository creates a new SQLite repository
func NewSQLiteRepository(dbPath string, config *config.Config) (*SQLiteRepository, error) {
	repo := &SQLiteRepository{
		dbPath:     dbPath,
		config:     config,
		spanPrefix: "user.repo.",
	}

	if err := repo.initializeDatabase(); err != nil {
		return nil, fmt.Errorf("failed to initialize database: %w", err)
	}

	return repo, nil
}

// initializeDatabase sets up the SQLite database and runs migrations
func (r *SQLiteRepository) initializeDatabase() error {
	// Create directory if it doesn't exist
	dir := filepath.Dir(r.dbPath)
	if err := os.MkdirAll(dir, 0755); err != nil {
		return fmt.Errorf("failed to create directory: %w", err)
	}

	// Configure GORM logger to use standard log
	gormLogger := logger.New(
		log.New(os.Stdout, "\n", log.LstdFlags),
		logger.Config{
			SlowThreshold:             time.Second,
			LogLevel:                  logger.Warn,
			IgnoreRecordNotFoundError: true,
			Colorful:                  true,
		},
	)

	var err error
	r.db, err = gorm.Open(sqlite.Open(r.dbPath), &gorm.Config{
		Logger: gormLogger,
	})
	if err != nil {
		return fmt.Errorf("failed to connect to database: %w", err)
	}

	// Auto-migrate the User model
	if err := r.db.AutoMigrate(&User{}); err != nil {
		return fmt.Errorf("failed to auto-migrate: %w", err)
	}

	return nil
}

// CreateUser creates a new user in the database
func (r *SQLiteRepository) CreateUser(ctx context.Context, user *User) error {
	// Create telemetry span
	ctx, span := r.createSpan(ctx, "create_user")
	if span != nil {
		defer span.End()
	}

	result := r.db.WithContext(ctx).Create(user)
	if result.Error != nil {
		if span != nil {
			span.RecordError(result.Error)
		}
		return fmt.Errorf("failed to create user: %w", result.Error)
	}
	return nil
}

// GetUserByUsername retrieves a user by username
func (r *SQLiteRepository) GetUserByUsername(ctx context.Context, username string) (*User, error) {
	// Create telemetry span
	ctx, span := r.createSpan(ctx, "get_user_by_username")
	if span != nil {
		defer span.End()
		span.SetAttributes(attribute.String("username", username))
	}

	var user User
	result := r.db.WithContext(ctx).Where("username = ?", username).First(&user)
	if result.Error != nil {
		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		if span != nil {
			span.RecordError(result.Error)
		}
		return nil, fmt.Errorf("failed to get user by username: %w", result.Error)
	}
	return &user, nil
}

// GetUserByID retrieves a user by ID
func (r *SQLiteRepository) GetUserByID(ctx context.Context, id uint) (*User, error) {
	var user User
	result := r.db.WithContext(ctx).First(&user, id)
	if result.Error != nil {
		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get user by ID: %w", result.Error)
	}
	return &user, nil
}

// UpdateUser updates a user in the database
func (r *SQLiteRepository) UpdateUser(ctx context.Context, user *User) error {
	result := r.db.WithContext(ctx).Save(user)
	if result.Error != nil {
		return fmt.Errorf("failed to update user: %w", result.Error)
	}
	return nil
}

// DeleteUser deletes a user from the database
func (r *SQLiteRepository) DeleteUser(ctx context.Context, id uint) error {
	result := r.db.WithContext(ctx).Delete(&User{}, id)
	if result.Error != nil {
		return fmt.Errorf("failed to delete user: %w", result.Error)
	}
	return nil
}

// AllowPasswordReset flags a user for password reset
func (r *SQLiteRepository) AllowPasswordReset(ctx context.Context, username string) error {
	user, err := r.GetUserByUsername(ctx, username)
	if err != nil {
		return fmt.Errorf("failed to get user for password reset: %w", err)
	}
	if user == nil {
		return fmt.Errorf("user not found: %s", username)
	}

	user.AllowPasswordReset = true
	return r.UpdateUser(ctx, user)
}

// CompletePasswordReset completes the password reset process
func (r *SQLiteRepository) CompletePasswordReset(ctx context.Context, username, newPasswordHash string) error {
	user, err := r.GetUserByUsername(ctx, username)
	if err != nil {
		return fmt.Errorf("failed to get user for password reset completion: %w", err)
	}
	if user == nil {
		return fmt.Errorf("user not found: %s", username)
	}

	if !user.AllowPasswordReset {
		return fmt.Errorf("password reset not allowed for user: %s", username)
	}

	user.PasswordHash = newPasswordHash
	user.AllowPasswordReset = false
	return r.UpdateUser(ctx, user)
}

// UserExists checks if a user exists by username
func (r *SQLiteRepository) UserExists(ctx context.Context, username string) (bool, error) {
	var count int64
	result := r.db.WithContext(ctx).Model(&User{}).Where("username = ?", username).Count(&count)
	if result.Error != nil {
		return false, fmt.Errorf("failed to check if user exists: %w", result.Error)
	}
	return count > 0, nil
}

// Close closes the database connection
func (r *SQLiteRepository) Close() error {
	sqlDB, err := r.db.DB()
	if err != nil {
		return fmt.Errorf("failed to get database connection: %w", err)
	}
	return sqlDB.Close()
}

// CheckDatabaseHealth checks if the database is healthy and responsive
func (r *SQLiteRepository) CheckDatabaseHealth(ctx context.Context) error {
	// Simple query to test database connectivity
	var count int64
	result := r.db.WithContext(ctx).Model(&User{}).Count(&count)
	if result.Error != nil {
		return fmt.Errorf("database health check failed: %w", result.Error)
	}
	return nil
}

// createSpan creates a new telemetry span if persistence telemetry is enabled
func (r *SQLiteRepository) createSpan(ctx context.Context, operation string) (context.Context, trace.Span) {
	if r.config == nil || !r.config.GetPersistenceTelemetryEnabled() {
		return ctx, trace.SpanFromContext(ctx)
	}

	// Create a new span with the operation name
	spanName := r.spanPrefix + operation
	tr := otel.Tracer("user-repository")
	return tr.Start(ctx, spanName)
}
