package db

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"io"
	"time"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// Database represents the database connection and services
type Database struct {
	DB        *gorm.DB
	secretKey string
}

// NewDatabase creates a new database connection
func NewDatabase(dsn string, secretKey string) (*Database, error) {
	db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Warn),
	})
	if err != nil {
		return nil, fmt.Errorf("failed to connect to database: %w", err)
	}

	// Auto-migrate schema
	err = db.AutoMigrate(&User{}, &MailboxSettings{}, &ProcessedEmail{})
	if err != nil {
		return nil, fmt.Errorf("failed to migrate database: %w", err)
	}

	return &Database{
		DB:        db,
		secretKey: secretKey,
	}, nil
}

// Close closes the database connection
func (d *Database) Close() error {
	sqlDB, err := d.DB.DB()
	if err != nil {
		return err
	}
	return sqlDB.Close()
}

// User operations

// CreateUser creates a new user
func (d *Database) CreateUser(email string) (*User, error) {
	user := &User{
		Email:     email,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}

	result := d.DB.Create(user)
	if result.Error != nil {
		return nil, fmt.Errorf("failed to create user: %w", result.Error)
	}

	return user, nil
}

// GetUserByEmail retrieves a user by email
func (d *Database) GetUserByEmail(email string) (*User, error) {
	var user User
	result := d.DB.Where("email = ?", email).First(&user)
	if result.Error != nil {
		return nil, fmt.Errorf("user not found: %w", result.Error)
	}
	return &user, nil
}

// UpdateUserOTP updates the user's OTP hash and expiry
func (d *Database) UpdateUserOTP(email, hashedOTP string, expiry time.Time) error {
	result := d.DB.Model(&User{}).
		Where("email = ?", email).
		Updates(map[string]interface{}{
			"hashed_otp": hashedOTP,
			"otp_expiry": expiry,
			"updated_at": time.Now(),
		})

	if result.Error != nil {
		return fmt.Errorf("failed to update OTP: %w", result.Error)
	}

	if result.RowsAffected == 0 {
		return fmt.Errorf("user not found: %s", email)
	}

	return nil
}

// ClearUserOTP clears the user's OTP
func (d *Database) ClearUserOTP(email string) error {
	result := d.DB.Model(&User{}).
		Where("email = ?", email).
		Updates(map[string]interface{}{
			"hashed_otp": nil,
			"otp_expiry": nil,
			"updated_at": time.Now(),
		})

	if result.Error != nil {
		return fmt.Errorf("failed to clear OTP: %w", result.Error)
	}

	return nil
}

// MailboxSettings operations

// GetUsersWithAutoStart returns all mailbox settings with AutoStart enabled,
// with IMAP passwords decrypted for immediate use
func (d *Database) GetUsersWithAutoStart() ([]MailboxSettings, error) {
	var settings []MailboxSettings
	result := d.DB.Where("auto_start = ?", true).Find(&settings)
	if result.Error != nil {
		return nil, fmt.Errorf("failed to query auto-start settings: %w", result.Error)
	}

	for i := range settings {
		decrypted, err := d.decrypt(settings[i].IMAPPassEncrypted)
		if err != nil {
			return nil, fmt.Errorf("failed to decrypt IMAP password for user %d: %w", settings[i].UserID, err)
		}
		settings[i].IMAPPassEncrypted = decrypted
	}

	return settings, nil
}

// GetMailboxSettings retrieves mailbox settings for a user
func (d *Database) GetMailboxSettings(userID uint) (*MailboxSettings, error) {
	var settings MailboxSettings
	result := d.DB.Where("user_id = ?", userID).First(&settings)
	if result.Error != nil {
		return nil, fmt.Errorf("mailbox settings not found: %w", result.Error)
	}

	// Decrypt passwords
	var err error
	settings.IMAPPassEncrypted, err = d.decrypt(settings.IMAPPassEncrypted)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt IMAP password: %w", err)
	}

	settings.SMTPPassEncrypted, err = d.decrypt(settings.SMTPPassEncrypted)
	if err != nil {
		return nil, fmt.Errorf("failed to decrypt SMTP password: %w", err)
	}

	return &settings, nil
}

// SaveMailboxSettings saves or updates mailbox settings for a user
func (d *Database) SaveMailboxSettings(settings *MailboxSettings) error {
	// Encrypt passwords
	var err error
	settings.IMAPPassEncrypted, err = d.encrypt(settings.IMAPPassEncrypted)
	if err != nil {
		return fmt.Errorf("failed to encrypt IMAP password: %w", err)
	}

	settings.SMTPPassEncrypted, err = d.encrypt(settings.SMTPPassEncrypted)
	if err != nil {
		return fmt.Errorf("failed to encrypt SMTP password: %w", err)
	}

	// Check if settings already exist
	var existing MailboxSettings
	result := d.DB.Where("user_id = ?", settings.UserID).First(&existing)

	if result.Error == nil {
		// Update existing
		settings.ID = existing.ID
		settings.UpdatedAt = time.Now()
		result = d.DB.Save(settings)
	} else {
		// Create new
		settings.CreatedAt = time.Now()
		settings.UpdatedAt = time.Now()
		result = d.DB.Create(settings)
	}

	if result.Error != nil {
		return fmt.Errorf("failed to save mailbox settings: %w", result.Error)
	}

	return nil
}

// UpdateLastProcessedUID updates the last processed UID for a user
func (d *Database) UpdateLastProcessedUID(userID uint, uid uint32) error {
	result := d.DB.Model(&MailboxSettings{}).
		Where("user_id = ?", userID).
		Update("last_processed_uid", uid)

	if result.Error != nil {
		return fmt.Errorf("failed to update last processed UID: %w", result.Error)
	}

	return nil
}

// ToggleTestMode toggles test mode for a user
func (d *Database) ToggleTestMode(userID uint, testMode bool) error {
	result := d.DB.Model(&MailboxSettings{}).
		Where("user_id = ?", userID).
		Update("test_mode", testMode)

	if result.Error != nil {
		return fmt.Errorf("failed to toggle test mode: %w", result.Error)
	}

	return nil
}

// ProcessedEmail operations

// CreateProcessedEmail records a processed email
func (d *Database) CreateProcessedEmail(email *ProcessedEmail) error {
	email.ProcessedAt = time.Now()
	result := d.DB.Create(email)
	if result.Error != nil {
		return fmt.Errorf("failed to create processed email record: %w", result.Error)
	}
	return nil
}

// GetFolderCounts returns counts of emails by folder for a user
func (d *Database) GetFolderCounts(userID uint) ([]FolderCount, error) {
	var counts []FolderCount
	result := d.DB.Model(&ProcessedEmail{}).
		Select("classified_folder as folder, count(*) as count").
		Where("user_id = ?", userID).
		Group("classified_folder").
		Scan(&counts)

	if result.Error != nil {
		return nil, fmt.Errorf("failed to get folder counts: %w", result.Error)
	}

	return counts, nil
}

// GetTotalProcessed returns total number of processed emails for a user
func (d *Database) GetTotalProcessed(userID uint) (int64, error) {
	var count int64
	result := d.DB.Model(&ProcessedEmail{}).
		Where("user_id = ?", userID).
		Count(&count)

	if result.Error != nil {
		return 0, fmt.Errorf("failed to get total processed count: %w", result.Error)
	}

	return count, nil
}

// GetLastProcessedTime returns the most recent processed email time for a user
func (d *Database) GetLastProcessedTime(userID uint) (*time.Time, error) {
	var email ProcessedEmail
	result := d.DB.Where("user_id = ?", userID).
		Order("processed_at desc").
		First(&email)

	if result.Error != nil {
		if result.Error == gorm.ErrRecordNotFound {
			return nil, nil
		}
		return nil, fmt.Errorf("failed to get last processed time: %w", result.Error)
	}

	return &email.ProcessedAt, nil
}

// Encryption helpers

func (d *Database) encrypt(plaintext string) (string, error) {
	if plaintext == "" {
		return "", nil
	}

	block, err := aes.NewCipher([]byte(d.secretKey[:32]))
	if err != nil {
		return "", err
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}

	nonce := make([]byte, gcm.NonceSize())
	if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
		return "", err
	}

	ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil)
	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

func (d *Database) decrypt(ciphertext string) (string, error) {
	if ciphertext == "" {
		return "", nil
	}

	data, err := base64.StdEncoding.DecodeString(ciphertext)
	if err != nil {
		return "", err
	}

	block, err := aes.NewCipher([]byte(d.secretKey[:32]))
	if err != nil {
		return "", err
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}

	nonceSize := gcm.NonceSize()
	if len(data) < nonceSize {
		return "", fmt.Errorf("ciphertext too short")
	}

	nonce, ciphertextBytes := data[:nonceSize], data[nonceSize:]
	plaintext, err := gcm.Open(nil, nonce, ciphertextBytes, nil)
	if err != nil {
		return "", err
	}

	return string(plaintext), nil
}