package mysql

import (
	"fmt"
	"os"
	"strings"

	"gorm.io/gorm"

	"security-center-backend/internal/model"
	alertmodel "security-center-backend/internal/modules/alerts/model"
	caasmmodel "security-center-backend/internal/modules/caasm/model"
	depmodel "security-center-backend/internal/modules/department/model"
	scanmodel "security-center-backend/internal/modules/scan/model"
	vulnmodel "security-center-backend/internal/modules/vuln/model"
)

const runtimeAutoMigrateEnvVar = "SECURITY_CENTER_RUNTIME_AUTO_MIGRATE"

func RuntimeAutoMigrateEnabled(appEnvironment string) bool {
	if value, ok := os.LookupEnv(runtimeAutoMigrateEnvVar); ok {
		if parsed, ok := normalizeBoolEnv(value); ok {
			return parsed
		}
	}

	switch normalizeEnvironmentName(appEnvironment) {
	case "production", "prod":
		return false
	default:
		return true
	}
}

func IsProductionEnvironment(appEnvironment string) bool {
	switch normalizeEnvironmentName(appEnvironment) {
	case "production", "prod":
		return true
	default:
		return false
	}
}

func AutoMigrate(db *gorm.DB) error {
	if err := normalizeLegacyDNSRecordOwnerEmployeeID(db); err != nil {
		return fmt.Errorf("failed to normalize dns_records.owner_employee_id before automigrate: %w", err)
	}

	models := []interface{}{
		&model.User{},
		&model.Role{},
		&model.Permission{},
		&model.SystemSettings{},
		&alertmodel.LarkAlertRule{},
		&alertmodel.LarkAlertRun{},
		&alertmodel.LarkAlertDelivery{},
		&alertmodel.AlertRuntimeOffset{},
		&depmodel.Department{},
		&depmodel.Employee{},
		&depmodel.OrgSyncRun{},
		&caasmmodel.CloudAccount{},
		&caasmmodel.Config{},
		&caasmmodel.Asset{},
		&caasmmodel.IPAsset{},
		&caasmmodel.DomainAsset{},
		&caasmmodel.DNSRecord{},
		&caasmmodel.DNSRecordComment{},
		&caasmmodel.IPPortIndex{},
		&caasmmodel.AssetRelationship{},
		&caasmmodel.AssetHistory{},
		&caasmmodel.AssetTag{},
		&caasmmodel.APIRouteConfig{},
		&caasmmodel.APIRouteAsset{},
		&caasmmodel.SyncJob{},
		&caasmmodel.SyncJobEvent{},
		&scanmodel.Campaign{},
		&scanmodel.Batch{},
		&scanmodel.Target{},
		&scanmodel.Observation{},
		&scanmodel.CallbackReceipt{},
		&vulnmodel.Vulnerability{},
		&vulnmodel.VulnerabilityAttachment{},
		&vulnmodel.VulnerabilityEvent{},
		&vulnmodel.VulnerabilityComment{},
		&vulnmodel.VulnerabilityType{},
		&vulnmodel.Source{},
		&vulnmodel.SLARule{},
		&vulnmodel.SubmissionRequiredField{},
		&vulnmodel.Tag{},
		&vulnmodel.VulnerabilityTagRelation{},
		&vulnmodel.DiscoveryChannel{},
		&model.AuditLog{},
		&model.APIKey{},
	}

	var errs []string
	for _, m := range models {
		if err := db.AutoMigrate(m); err != nil {
			errs = append(errs, fmt.Sprintf("%T: %v", m, err))
		}
	}

	if len(errs) > 0 {
		return fmt.Errorf("auto migrate encountered errors (remaining models continued): %s", strings.Join(errs, "; "))
	}

	return nil
}

type mysqlColumnInfo struct {
	Field string
	Type  string
}

func normalizeLegacyDNSRecordOwnerEmployeeID(db *gorm.DB) error {
	if !db.Migrator().HasTable("dns_records") {
		return nil
	}
	if !db.Migrator().HasColumn(&caasmmodel.DNSRecord{}, "owner_employee_id") {
		return nil
	}

	var column mysqlColumnInfo
	if err := db.Raw("SHOW COLUMNS FROM dns_records LIKE 'owner_employee_id'").Scan(&column).Error; err != nil {
		return err
	}
	if column.Field == "" {
		return nil
	}

	columnType := strings.ToLower(column.Type)
	if strings.Contains(columnType, "bigint") {
		return nil
	}

	if err := db.Exec(`
		UPDATE dns_records
		SET owner_employee_id = NULL
		WHERE owner_employee_id IS NOT NULL
		  AND (TRIM(owner_employee_id) = '' OR TRIM(owner_employee_id) NOT REGEXP '^[0-9]+$')
	`).Error; err != nil {
		return err
	}

	return db.Exec(`
		ALTER TABLE dns_records
		  MODIFY COLUMN owner_employee_id BIGINT UNSIGNED NULL
	`).Error
}

func normalizeEnvironmentName(appEnvironment string) string {
	return strings.ToLower(strings.TrimSpace(appEnvironment))
}

func normalizeBoolEnv(value string) (bool, bool) {
	switch normalizeEnvironmentName(value) {
	case "1", "true", "yes", "y", "on":
		return true, true
	case "0", "false", "no", "n", "off":
		return false, true
	default:
		return false, false
	}
}
