package mysql

import (
	"context"
	"errors"
	"fmt"
	"regexp"
	"strings"

	mysqldriver "github.com/go-sql-driver/mysql"
	"gorm.io/gorm"

	"wucher/internal/shared/pkg/apperrors"
)

type fkReferenceRow struct {
	TableName  string `gorm:"column:table_name"`
	ColumnName string `gorm:"column:column_name"`
}

var mysqlDeleteRefTableRegexp = regexp.MustCompile("fails \\(`[^`]+`\\.`([^`]+)`")

func ensureNoReferenceBeforeDelete(ctx context.Context, db *gorm.DB, tableName string, id []byte) error {
	if db == nil || db.Dialector == nil || db.Dialector.Name() != "mysql" {
		return nil
	}
	if len(id) != 16 || strings.TrimSpace(tableName) == "" {
		return nil
	}

	refs := make([]fkReferenceRow, 0)
	err := db.WithContext(ctx).Raw(`
SELECT kcu.TABLE_NAME AS table_name, kcu.COLUMN_NAME AS column_name
FROM information_schema.KEY_COLUMN_USAGE kcu
WHERE kcu.TABLE_SCHEMA = DATABASE()
  AND kcu.REFERENCED_TABLE_SCHEMA = DATABASE()
  AND kcu.REFERENCED_TABLE_NAME = ?
  AND kcu.REFERENCED_COLUMN_NAME = 'id'
`, tableName).Scan(&refs).Error
	if err != nil {
		return err
	}

	related := make([]string, 0)
	for i := range refs {
		query := fmt.Sprintf("SELECT 1 FROM `%s` WHERE `%s` = ? LIMIT 1", refs[i].TableName, refs[i].ColumnName)
		var one int
		rowErr := db.WithContext(ctx).Raw(query, id).Scan(&one).Error
		if rowErr != nil {
			return rowErr
		}
		if one == 1 {
			related = append(related, moduleNameFromTable(refs[i].TableName))
		}
	}

	if len(related) > 0 {
		return apperrors.NewDeleteConflictError(related...)
	}
	return nil
}

func mapDeleteConstraintError(err error) error {
	if err == nil {
		return nil
	}
	if apperrors.IsDeleteConflict(err) {
		return err
	}
	var mysqlErr *mysqldriver.MySQLError
	if !strings.Contains(strings.ToLower(err.Error()), "foreign key") && !strings.Contains(strings.ToLower(err.Error()), "referenced") && !strings.Contains(strings.ToLower(err.Error()), "constraint") && !strings.Contains(strings.ToLower(err.Error()), "delete") {
		return err
	}
	if !asMySQLError(err, &mysqlErr) || mysqlErr.Number != 1451 {
		return err
	}
	if matches := mysqlDeleteRefTableRegexp.FindStringSubmatch(mysqlErr.Message); len(matches) > 1 {
		return apperrors.NewDeleteConflictError(moduleNameFromTable(matches[1]))
	}
	return apperrors.NewDeleteConflictError()
}

func asMySQLError(err error, target **mysqldriver.MySQLError) bool {
	if err == nil {
		return false
	}
	return errors.As(err, target)
}

func moduleNameFromTable(tableName string) string {
	tableName = strings.TrimSpace(tableName)
	if tableName == "" {
		return "another module"
	}
	switch tableName {
	case "takeover_acs":
		return "takeover"
	case "base_operational_shift_times":
		return "base operational shift times"
	case "base_contact_roles":
		return "base contact roles"
	case "duls":
		return "dul"
	}
	return strings.ReplaceAll(tableName, "_", " ")
}
