package db

import (
	"fmt"
	"log/slog"
	"os"
	"strings"

	"github.com/tidbcloud/aisupport/services/oncall/models"

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

var DB *gorm.DB

func safeDSNForLog(dialect, dsn string) string {
	if strings.ToLower(strings.TrimSpace(dialect)) != "mysql" {
		return dsn
	}
	at := strings.Index(dsn, "@")
	if at <= 0 {
		return dsn
	}
	cred := dsn[:at]
	sep := strings.Index(cred, ":")
	if sep < 0 {
		return dsn
	}
	return cred[:sep] + ":***" + dsn[at:]
}

func InitDB(dialect, dsn string) error {
	d := strings.ToLower(strings.TrimSpace(dialect))
	if d == "" {
		d = "sqlite"
	}

	if dsn == "" {
		if d == "sqlite" || d == "sqlite3" {
			dsn = "oncall.db"
		} else {
			return fmt.Errorf("missing ONCALL_DB_DSN for dialect=%s", d)
		}
	}

	slog.Info("Connecting to database...", "dialect", d, "dsn", safeDSNForLog(d, dsn))

	var dialector gorm.Dialector
	switch d {
	case "sqlite", "sqlite3":
		dialector = sqlite.Open(dsn)
	case "mysql":
		dialector = mysql.Open(dsn)
	default:
		return fmt.Errorf("unsupported ONCALL_DB_DIALECT=%s", d)
	}

	var err error
	DB, err = gorm.Open(dialector, &gorm.Config{
		Logger: gormlogger.Default.LogMode(gormlogger.Info),
	})
	if err != nil {
		return err
	}

	if err := DB.AutoMigrate(
		&models.Engineer{},
		&models.Team{},
		&models.TeamLayer{},
		&models.RealizedShift{},
		&models.EngineerTeam{},
		&models.ShiftInterval{},
		&models.AuditLog{},
		&models.ApiKey{},
		&models.SuperAdmin{},
		&models.User{},
		&models.UserSession{},
		&models.NotificationLog{},
	); err != nil {
		return fmt.Errorf("auto migrate failed: %w", err)
	}
	if err := backfillGroupNames(); err != nil {
		return fmt.Errorf("backfill group_names failed: %w", err)
	}
	if err := backfillEngineerTeamRoles(); err != nil {
		return fmt.Errorf("backfill engineer_teams.role failed: %w", err)
	}
	seedData()
	return nil
}

// backfillGroupNames migrates the legacy single GroupName column into the
// GroupNames list for any team that doesn't have it set yet (one-time, idempotent).
func backfillGroupNames() error {
	var teams []models.Team
	if err := DB.Find(&teams).Error; err != nil {
		return err
	}
	for _, t := range teams {
		if len(t.GroupNames) > 0 {
			continue
		}
		group := strings.TrimSpace(t.GroupName)
		if group == "" {
			group = "Other"
		}
		if err := DB.Model(&models.Team{}).Where("id = ?", t.ID).
			Update("group_names", models.StringList{group}).Error; err != nil {
			return err
		}
	}
	return nil
}

// backfillEngineerTeamRoles promotes rows that predate the role column: adding
// the column defaults everyone to member, so the legacy is_manager flag decides
// who becomes a manager. Safe to re-run — service.setMemberRole keeps
// is_manager mirrored, so a later demotion is not undone on the next start.
func backfillEngineerTeamRoles() error {
	// Don't depend on how the column default rendered for pre-existing rows.
	if err := DB.Model(&models.EngineerTeam{}).
		Where("role IS NULL OR role = ?", "").
		Update("role", models.TeamRoleMember).Error; err != nil {
		return err
	}
	if !DB.Migrator().HasColumn(&models.EngineerTeam{}, "is_manager") {
		return nil
	}
	return DB.Model(&models.EngineerTeam{}).
		Where("is_manager = ? AND role = ?", true, models.TeamRoleMember).
		Update("role", models.TeamRoleManager).Error
}

func seedData() {
	// Seed test data only for local sqlite development.
	dialect := strings.ToLower(strings.TrimSpace(os.Getenv("ONCALL_DB_DIALECT")))
	if dialect == "" || dialect == "sqlite" || dialect == "sqlite3" {
		var c models.Team
		if err := DB.First(&c, 1).Error; err != nil {
			c = models.Team{ID: 1, Name: "L1 (SE)", DisplayName: "L1 (SE)", GroupNames: models.StringList{"Other"}}
			c2 := models.Team{ID: 2, Name: "L2 (SEE)", DisplayName: "L2 (SEE)", GroupNames: models.StringList{"Other"}}
			c3 := models.Team{ID: 3, Name: "Oncall", DisplayName: "Oncall", GroupNames: models.StringList{"Other"}}
			DB.Create(&c)
			DB.Create(&c2)
			DB.Create(&c3)

			e1 := models.Engineer{ID: 1, Name: "Alice", Email: "alice@example.com"}
			e2 := models.Engineer{ID: 2, Name: "Bob", Email: "bob@example.com"}
			e3 := models.Engineer{ID: 3, Name: "Charlie", Email: "charlie@example.com"}
			DB.Create(&e1)
			DB.Create(&e2)
			DB.Create(&e3)

			ec1 := models.EngineerTeam{EngineerID: e1.ID, TeamID: c.ID, Role: models.TeamRoleMember}
			ec2 := models.EngineerTeam{EngineerID: e2.ID, TeamID: c.ID, Role: models.TeamRoleMember}
			ec3 := models.EngineerTeam{EngineerID: e3.ID, TeamID: c2.ID, Role: models.TeamRoleMember}
			DB.Create(&ec1)
			DB.Create(&ec2)
			DB.Create(&ec3)

			slog.Info("Database Seeded with test data.")
		}
	}

	// Bootstrap initial super admin only when table is empty and explicitly configured.
	var n int64
	if err := DB.Model(&models.SuperAdmin{}).Count(&n).Error; err == nil && n == 0 {
		seed := strings.ToLower(strings.TrimSpace(os.Getenv("INITIAL_SUPER_ADMIN_EMAIL")))
		if seed != "" {
			_ = DB.Create(&models.SuperAdmin{Email: seed, CreatedBy: "bootstrap"}).Error
		}
	}
}
