package database

import (
	"fmt"
	"log"
	"os"
	"time"

	"github.com/openaccounting/oa-server/core/util/id"
	"github.com/openaccounting/oa-server/models"
	"gorm.io/driver/mysql"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

var DB *gorm.DB

type Config struct {
	Driver   string // "mysql" or "sqlite"
	Host     string
	Port     string
	User     string
	Password string
	DBName   string
	SSLMode  string
	// SQLite specific
	File string // SQLite database file path
}

func Connect(config *Config) error {
	// Configure GORM logger
	newLogger := logger.New(
		log.New(os.Stdout, "\r\n", log.LstdFlags),
		logger.Config{
			SlowThreshold:             time.Second,
			LogLevel:                  logger.Info,
			IgnoreRecordNotFoundError: true,
			Colorful:                  true,
		},
	)

	var db *gorm.DB
	var err error

	// Choose driver based on config
	switch config.Driver {
	case "sqlite":
		// Use SQLite
		dbFile := config.File
		if dbFile == "" {
			dbFile = "./openaccounting.db" // Default SQLite file
		}
		db, err = gorm.Open(sqlite.Open(dbFile), &gorm.Config{
			Logger: newLogger,
		})
	case "mysql":
		// Use MySQL (existing logic)
		dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
			config.User, config.Password, config.Host, config.Port, config.DBName)
		db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
			Logger: newLogger,
		})
	default:
		return fmt.Errorf("unsupported database driver: %s (supported: mysql, sqlite)", config.Driver)
	}

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

	// Configure connection pool (only for MySQL, SQLite handles this internally)
	if config.Driver == "mysql" {
		sqlDB, err := db.DB()
		if err != nil {
			return fmt.Errorf("failed to get database instance: %w", err)
		}

		sqlDB.SetMaxOpenConns(25)
		sqlDB.SetMaxIdleConns(25)
		sqlDB.SetConnMaxLifetime(5 * time.Minute)
	}

	DB = db
	return nil
}

// AutoMigrate runs automatic migrations for all models
func AutoMigrate() error {
	return DB.AutoMigrate(
		&models.Org{},
		&models.User{},
		&models.UserOrg{},
		&models.Token{},
		&models.Account{},
		&models.Transaction{},
		&models.Split{},
		&models.Balance{},
		&models.Permission{},
		&models.Price{},
		&models.Session{},
		&models.APIKey{},
		&models.Invite{},
		&models.BudgetItem{},
	)
}

// Migrate runs custom migrations
func Migrate() error {
	// Create indexes
	if err := createIndexes(); err != nil {
		return err
	}

	// Insert default data - temporarily disabled for testing
	// if err := seedDefaultData(); err != nil {
	//	return err
	// }

	return nil
}

func createIndexes() error {
	// Create custom indexes that GORM doesn't handle automatically
	// Based on original indexes.sql file
	indexes := []string{
		// Original indexes from indexes.sql
		"CREATE INDEX IF NOT EXISTS account_orgId_index ON accounts(orgId)",
		"CREATE INDEX IF NOT EXISTS split_accountId_index ON splits(accountId)",
		"CREATE INDEX IF NOT EXISTS split_transactionId_index ON splits(transactionId)",
		"CREATE INDEX IF NOT EXISTS split_date_index ON splits(date)",
		"CREATE INDEX IF NOT EXISTS split_updated_index ON splits(updated)",
		"CREATE INDEX IF NOT EXISTS budgetitem_orgId_index ON budget_items(orgId)",
		
		// Additional useful indexes for performance
		"CREATE INDEX IF NOT EXISTS idx_transaction_date ON transactions(date)",
		"CREATE INDEX IF NOT EXISTS idx_transaction_org ON transactions(orgId)",
		"CREATE INDEX IF NOT EXISTS idx_account_parent ON accounts(parent)",
		"CREATE INDEX IF NOT EXISTS idx_userorg_user ON user_orgs(userId)",
		"CREATE INDEX IF NOT EXISTS idx_userorg_org ON user_orgs(orgId)",
		"CREATE INDEX IF NOT EXISTS idx_balance_account_date ON balances(accountId, date)",
		"CREATE INDEX IF NOT EXISTS idx_permission_org_account ON permissions(orgId, accountId)",
	}

	for _, idx := range indexes {
		if err := DB.Exec(idx).Error; err != nil {
			return fmt.Errorf("failed to create index: %w", err)
		}
	}

	return nil
}

func seedDefaultData() error {
	// Check if we already have data
	var count int64
	if err := DB.Model(&models.Org{}).Count(&count).Error; err != nil {
		return err
	}

	if count > 0 {
		return nil // Data already exists
	}

	// Create a default organization
	defaultOrg := models.Org{
		ID:        id.New(), // You'll need to implement this
		Inserted:  uint64(time.Now().UnixNano() / int64(time.Millisecond)),
		Updated:   uint64(time.Now().UnixNano() / int64(time.Millisecond)),
		Name:      "Default Organization",
		Currency:  "USD",
		Precision: 2,
		Timezone:  "UTC",
	}

	if err := DB.Create(&defaultOrg).Error; err != nil {
		return fmt.Errorf("failed to create default organization: %w", err)
	}

	// Create default accounts for the organization
	defaultAccounts := []models.Account{
		{
			ID:           id.New(),
			OrgID:        defaultOrg.ID,
			Inserted:     uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Updated:      uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Name:         "Assets",
			Parent:       []byte{0}, // Root account has zero parent
			Currency:     "USD",
			Precision:    2,
			DebitBalance: true,
		},
		{
			ID:           id.New(),
			OrgID:        defaultOrg.ID,
			Inserted:     uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Updated:      uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Name:         "Liabilities",
			Parent:       []byte{0},
			Currency:     "USD",
			Precision:    2,
			DebitBalance: false,
		},
		{
			ID:           id.New(),
			OrgID:        defaultOrg.ID,
			Inserted:     uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Updated:      uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Name:         "Equity",
			Parent:       []byte{0},
			Currency:     "USD",
			Precision:    2,
			DebitBalance: false,
		},
		{
			ID:           id.New(),
			OrgID:        defaultOrg.ID,
			Inserted:     uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Updated:      uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Name:         "Revenue",
			Parent:       []byte{0},
			Currency:     "USD",
			Precision:    2,
			DebitBalance: false,
		},
		{
			ID:           id.New(),
			OrgID:        defaultOrg.ID,
			Inserted:     uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Updated:      uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Name:         "Expenses",
			Parent:       []byte{0},
			Currency:     "USD",
			Precision:    2,
			DebitBalance: true,
		},
	}

	// Create accounts and store their IDs for parent-child relationships
	accountMap := make(map[string]*models.Account)

	for _, acc := range defaultAccounts {
		account := acc
		if err := DB.Create(&account).Error; err != nil {
			return fmt.Errorf("failed to create account %s: %w", acc.Name, err)
		}
		accountMap[acc.Name] = &account
	}

	// Create Current Assets first
	var assetsParent []byte
	if assetsAccount, exists := accountMap["Assets"]; exists {
		assetsParent = assetsAccount.ID
	} else {
		return fmt.Errorf("Assets account not found in accountMap")
	}
	
	currentAssets := models.Account{
		ID:           id.New(),
		OrgID:        defaultOrg.ID,
		Inserted:     uint64(time.Now().UnixNano() / int64(time.Millisecond)),
		Updated:      uint64(time.Now().UnixNano() / int64(time.Millisecond)),
		Name:         "Current Assets",
		Parent:       assetsParent,
		Currency:     "USD",
		Precision:    2,
		DebitBalance: true,
	}
	
	if err := DB.Create(&currentAssets).Error; err != nil {
		return fmt.Errorf("failed to create Current Assets: %w", err)
	}
	accountMap["Current Assets"] = &currentAssets
	
	// Create Accounts Payable
	var liabilitiesParent []byte
	if liabilitiesAccount, exists := accountMap["Liabilities"]; exists {
		liabilitiesParent = liabilitiesAccount.ID
	} else {
		return fmt.Errorf("Liabilities account not found in accountMap")
	}
	
	accountsPayable := models.Account{
		ID:           id.New(),
		OrgID:        defaultOrg.ID,
		Inserted:     uint64(time.Now().UnixNano() / int64(time.Millisecond)),
		Updated:      uint64(time.Now().UnixNano() / int64(time.Millisecond)),
		Name:         "Accounts Payable",
		Parent:       liabilitiesParent,
		Currency:     "USD",
		Precision:    2,
		DebitBalance: false,
	}
	
	if err := DB.Create(&accountsPayable).Error; err != nil {
		return fmt.Errorf("failed to create Accounts Payable: %w", err)
	}
	
	// Now create sub-accounts under Current Assets
	subAccounts := []models.Account{
		{
			ID:           id.New(),
			OrgID:        defaultOrg.ID,
			Inserted:     uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Updated:      uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Name:         "Cash",
			Parent:       currentAssets.ID,
			Currency:     "USD",
			Precision:    2,
			DebitBalance: true,
		},
		{
			ID:           id.New(),
			OrgID:        defaultOrg.ID,
			Inserted:     uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Updated:      uint64(time.Now().UnixNano() / int64(time.Millisecond)),
			Name:         "Accounts Receivable",
			Parent:       currentAssets.ID,
			Currency:     "USD",
			Precision:    2,
			DebitBalance: true,
		},
	}

	for _, acc := range subAccounts {
		if err := DB.Create(&acc).Error; err != nil {
			return fmt.Errorf("failed to create sub-account %s: %w", acc.Name, err)
		}
	}

	return nil
}
