package models

import (
	"context"
	"errors"
	"fmt"
	"log"
	"reflect"
	"strings"
	"time"

	"github.com/timetracker/backend/internal/config"
	"github.com/timetracker/backend/internal/db"
	"github.com/timetracker/backend/internal/permissions"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// MigrateDB performs database migrations for all models
func MigrateDB() error {
	gormDB := db.GetEngine(context.Background())
	if gormDB == nil {
		return errors.New("database not initialized")
	}

	log.Println("Starting database migration...")

	// Add all models that should be migrated here
	err := gormDB.AutoMigrate(
		&Company{},
		&User{},
		&Customer{},
		&Project{},
		&Activity{},
		&TimeEntry{},
		&permissions.Role{},
		&permissions.Policy{},
	)

	if err != nil {
		return fmt.Errorf("error migrating database: %w", err)
	}

	log.Println("Database migration completed successfully")
	return nil
}

// GetGormDB is used for special cases like database creation
func GetGormDB(dbConfig config.DatabaseConfig, dbName string) (*gorm.DB, error) {
	dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
		dbConfig.Host, dbConfig.Port, dbConfig.User, dbConfig.Password, dbName, dbConfig.SSLMode)

	// Configure GORM logger
	gormLogger := logger.New(
		log.New(log.Writer(), "\r\n", log.LstdFlags), // io writer
		logger.Config{
			SlowThreshold:             200 * time.Millisecond, // Slow SQL threshold
			LogLevel:                  dbConfig.LogLevel,      // Log level
			IgnoreRecordNotFoundError: true,                   // Ignore ErrRecordNotFound error for logger
			Colorful:                  true,                   // Enable color
		},
	)

	db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
		Logger: gormLogger,
	})
	if err != nil {
		return nil, fmt.Errorf("error connecting to the database: %w", err)
	}

	return db, nil
}

// UpdateModel updates a model based on the set pointer fields
func UpdateModel(ctx context.Context, model any, updates any) error {
	updateValue := reflect.ValueOf(updates)

	// If updates is a pointer, use the value behind it
	if updateValue.Kind() == reflect.Ptr {
		updateValue = updateValue.Elem()
	}

	// Make sure updates is a struct
	if updateValue.Kind() != reflect.Struct {
		return errors.New("updates must be a struct")
	}

	updateType := updateValue.Type()
	updateMap := make(map[string]any)

	// Iterate through all fields
	for i := range updateValue.NumField() {
		field := updateValue.Field(i)
		fieldType := updateType.Field(i)

		// Skip unexported fields
		if !fieldType.IsExported() {
			continue
		}

		// Special case: Skip ID field (use only for updates)
		if fieldType.Name == "ID" {
			continue
		}

		// For pointer types, check if they are not nil
		if field.Kind() == reflect.Ptr && !field.IsNil() {
			// Extract field name from GORM tag or use default field name
			fieldName := fieldType.Name

			if tag, ok := fieldType.Tag.Lookup("gorm"); ok {
				// Separate tag options
				options := strings.Split(tag, ";")
				for _, option := range options {
					if strings.HasPrefix(option, "column:") {
						fieldName = strings.TrimPrefix(option, "column:")
						break
					}
				}
			}

			// Use the value behind the pointer
			updateMap[fieldName] = field.Elem().Interface()
		}
	}

	if len(updateMap) == 0 {
		return nil // Nothing to update
	}

	return db.GetEngine(ctx).Model(model).Updates(updateMap).Error
}

// InitDB and CloseDB are forwarded to the db package for backward compatibility
func InitDB(config config.DatabaseConfig) error {
	return db.InitDB(config)
}

func CloseDB() error {
	return db.CloseDB()
}
