//go:build gonavi_full_drivers || gonavi_iris_driver

package db

import (
	"context"
	"database/sql"
	"fmt"
	"net"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"

	"GoNavi-Wails/internal/connection"
	"GoNavi-Wails/internal/logger"
	"GoNavi-Wails/internal/ssh"
	"GoNavi-Wails/internal/utils"

	_ "github.com/caretdev/go-irisnative"
)

const (
	defaultIRISPort      = 1972
	defaultIRISNamespace = "USER"
)

type IrisDB struct {
	conn        *sql.DB
	pingTimeout time.Duration
	namespace   string
	forwarder   *ssh.LocalForwarder
}

type irisTableRef struct {
	Schema string
	Table  string
}

func normalizeIRISNamespace(namespace string) string {
	trimmed := strings.Trim(strings.TrimSpace(namespace), "/")
	if trimmed == "" {
		return defaultIRISNamespace
	}
	return trimmed
}

func applyIRISURI(config connection.ConnectionConfig) connection.ConnectionConfig {
	parsed, ok := parseConnectionURI(config.URI, "iris", "intersystems")
	if !ok || parsed == nil {
		return config
	}
	next := config
	if host := strings.TrimSpace(parsed.Hostname()); host != "" {
		next.Host = host
	}
	if portText := strings.TrimSpace(parsed.Port()); portText != "" {
		if port, err := strconv.Atoi(portText); err == nil && port > 0 {
			next.Port = port
		}
	}
	if parsed.User != nil {
		next.User = parsed.User.Username()
		if password, ok := parsed.User.Password(); ok {
			next.Password = password
		}
	}
	if namespace := strings.Trim(strings.TrimSpace(parsed.Path), "/"); namespace != "" {
		next.Database = namespace
	}
	return next
}

func (i *IrisDB) getDSN(config connection.ConnectionConfig) string {
	namespace := normalizeIRISNamespace(config.Database)
	port := config.Port
	if port <= 0 {
		port = defaultIRISPort
	}

	u := &url.URL{
		Scheme: "iris",
		Host:   net.JoinHostPort(config.Host, strconv.Itoa(port)),
		Path:   "/" + namespace,
	}
	u.User = url.UserPassword(config.User, config.Password)

	q := url.Values{}
	mergeConnectionParamsFromConfig(q, config, "iris", "intersystems")
	u.RawQuery = q.Encode()
	return u.String()
}

func (i *IrisDB) Connect(config connection.ConnectionConfig) error {
	runConfig := applyIRISURI(config)
	if runConfig.Port <= 0 {
		runConfig.Port = defaultIRISPort
	}
	i.namespace = normalizeIRISNamespace(runConfig.Database)

	cleanupOnFailure := true
	defer func() {
		if !cleanupOnFailure {
			return
		}
		if i.conn != nil {
			_ = i.conn.Close()
			i.conn = nil
		}
		if i.forwarder != nil {
			_ = i.forwarder.Close()
			i.forwarder = nil
		}
	}()

	if runConfig.UseSSH {
		logger.Infof("InterSystems IRIS 使用 SSH 连接：地址=%s:%d 用户=%s", runConfig.Host, runConfig.Port, runConfig.User)
		forwarder, err := ssh.GetOrCreateLocalForwarder(runConfig.SSH, runConfig.Host, runConfig.Port)
		if err != nil {
			return fmt.Errorf("创建 SSH 隧道失败：%w", err)
		}
		i.forwarder = forwarder

		host, portStr, err := net.SplitHostPort(forwarder.LocalAddr)
		if err != nil {
			return fmt.Errorf("解析本地转发地址失败：%w", err)
		}
		port, err := strconv.Atoi(portStr)
		if err != nil {
			return fmt.Errorf("解析本地端口失败：%w", err)
		}

		runConfig.Host = host
		runConfig.Port = port
		runConfig.UseSSH = false
		logger.Infof("InterSystems IRIS 通过本地端口转发连接：%s -> %s:%d", forwarder.LocalAddr, config.Host, config.Port)
	}

	db, err := sql.Open("iris", i.getDSN(runConfig))
	if err != nil {
		return fmt.Errorf("打开数据库连接失败：%w", err)
	}
	i.conn = db
	i.pingTimeout = getConnectTimeout(runConfig)
	if err := i.Ping(); err != nil {
		return fmt.Errorf("连接建立后验证失败：%w", err)
	}
	cleanupOnFailure = false
	return nil
}

func (i *IrisDB) Close() error {
	if i.forwarder != nil {
		if err := i.forwarder.Close(); err != nil {
			logger.Warnf("关闭 InterSystems IRIS SSH 端口转发失败：%v", err)
		}
		i.forwarder = nil
	}
	if i.conn != nil {
		return i.conn.Close()
	}
	return nil
}

func (i *IrisDB) Ping() error {
	if i.conn == nil {
		return fmt.Errorf("连接未打开")
	}
	timeout := i.pingTimeout
	if timeout <= 0 {
		timeout = 5 * time.Second
	}
	ctx, cancel := utils.ContextWithTimeout(timeout)
	defer cancel()
	return i.conn.PingContext(ctx)
}

func (i *IrisDB) QueryMulti(query string) ([]connection.ResultSetData, error) {
	if i.conn == nil {
		return nil, fmt.Errorf("连接未打开")
	}
	rows, err := i.conn.Query(query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanMultiRows(rows)
}

func (i *IrisDB) QueryMultiContext(ctx context.Context, query string) ([]connection.ResultSetData, error) {
	if i.conn == nil {
		return nil, fmt.Errorf("连接未打开")
	}
	rows, err := i.conn.QueryContext(ctx, query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanMultiRows(rows)
}

func (i *IrisDB) QueryContext(ctx context.Context, query string) ([]map[string]interface{}, []string, error) {
	if i.conn == nil {
		return nil, nil, fmt.Errorf("连接未打开")
	}
	rows, err := i.conn.QueryContext(ctx, query)
	if err != nil {
		return nil, nil, err
	}
	defer rows.Close()
	return scanRows(rows)
}

func (i *IrisDB) Query(query string) ([]map[string]interface{}, []string, error) {
	if i.conn == nil {
		return nil, nil, fmt.Errorf("连接未打开")
	}
	rows, err := i.conn.Query(query)
	if err != nil {
		return nil, nil, err
	}
	defer rows.Close()
	return scanRows(rows)
}

func (i *IrisDB) ExecContext(ctx context.Context, query string) (int64, error) {
	if i.conn == nil {
		return 0, fmt.Errorf("连接未打开")
	}
	res, err := i.conn.ExecContext(ctx, query)
	if err != nil {
		return 0, err
	}
	return res.RowsAffected()
}

func (i *IrisDB) ExecBatchContext(ctx context.Context, query string) (int64, error) {
	return i.ExecContext(ctx, query)
}

func (i *IrisDB) Exec(query string) (int64, error) {
	if i.conn == nil {
		return 0, fmt.Errorf("连接未打开")
	}
	res, err := i.conn.Exec(query)
	if err != nil {
		return 0, err
	}
	return res.RowsAffected()
}

func (i *IrisDB) GetDatabases() ([]string, error) {
	namespace := strings.TrimSpace(i.namespace)
	if namespace != "" {
		return []string{namespace}, nil
	}
	data, _, err := i.Query(`SELECT DISTINCT TABLE_CATALOG FROM INFORMATION_SCHEMA.TABLES`)
	if err != nil {
		return nil, err
	}
	var namespaces []string
	seen := map[string]struct{}{}
	for _, row := range data {
		name := strings.TrimSpace(rowString(row, "TABLE_CATALOG", "table_catalog"))
		if name == "" {
			continue
		}
		if _, ok := seen[name]; ok {
			continue
		}
		seen[name] = struct{}{}
		namespaces = append(namespaces, name)
	}
	sort.Strings(namespaces)
	return namespaces, nil
}

func (i *IrisDB) GetTables(dbName string) ([]string, error) {
	data, _, err := i.Query(`SELECT * FROM INFORMATION_SCHEMA.TABLES`)
	if err != nil {
		return nil, err
	}
	var tables []string
	seen := map[string]struct{}{}
	for _, row := range data {
		tableType := strings.ToUpper(strings.TrimSpace(rowString(row, "TABLE_TYPE", "table_type")))
		if tableType != "" && tableType != "TABLE" && tableType != "BASE TABLE" {
			continue
		}
		schema := strings.TrimSpace(rowString(row, "TABLE_SCHEMA", "table_schema", "SCHEMA_NAME", "schema_name"))
		table := strings.TrimSpace(rowString(row, "TABLE_NAME", "table_name"))
		if table == "" || isIRISSystemSchema(schema) {
			continue
		}
		name := table
		if schema != "" {
			name = schema + "." + table
		}
		if _, ok := seen[name]; ok {
			continue
		}
		seen[name] = struct{}{}
		tables = append(tables, name)
	}
	sort.Strings(tables)
	return tables, nil
}

func (i *IrisDB) GetCreateStatement(dbName, tableName string) (string, error) {
	ref, err := parseIRISTableRef(dbName, tableName)
	if err != nil {
		return "", err
	}
	columns, err := i.GetColumns(dbName, tableName)
	if err != nil {
		return "", err
	}
	if len(columns) == 0 {
		return "", fmt.Errorf("未找到表字段：%s", tableName)
	}
	indexes, idxErr := i.GetIndexes(dbName, tableName)
	if idxErr != nil {
		indexes = nil
	}
	return buildIRISCreateTableDDL(ref, columns, indexes), nil
}

func (i *IrisDB) GetColumns(dbName, tableName string) ([]connection.ColumnDefinition, error) {
	ref, err := parseIRISTableRef(dbName, tableName)
	if err != nil {
		return nil, err
	}
	data, _, err := i.Query(buildIRISInfoSchemaWhereQuery("INFORMATION_SCHEMA.COLUMNS", ref))
	if err != nil {
		return nil, err
	}
	indexes, _ := i.GetIndexes(dbName, tableName)
	keyByColumn := irisColumnKeyMap(indexes)

	columns := make([]connection.ColumnDefinition, 0, len(data))
	for _, row := range data {
		name := strings.TrimSpace(rowString(row, "COLUMN_NAME", "column_name"))
		if name == "" {
			continue
		}
		key := keyByColumn[name]
		if primary, ok := irisBoolFromRow(row, "PRIMARY_KEY", "primary_key"); ok && primary {
			key = "PRI"
		} else if key == "" {
			if unique, ok := irisBoolFromRow(row, "UNIQUE_COLUMN", "unique_column", "IS_UNIQUE", "is_unique", "UNIQUE", "unique"); ok && unique {
				key = "UNI"
			}
		}
		col := connection.ColumnDefinition{
			Name:     name,
			Type:     buildIRISColumnType(row),
			Nullable: normalizeIRISNullable(rowString(row, "IS_NULLABLE", "is_nullable")),
			Key:      key,
			Extra:    "",
			Comment:  rowString(row, "DESCRIPTION", "description", "COMMENT", "comment"),
		}
		if rawDefault, ok := rowValue(row, "COLUMN_DEFAULT", "column_default"); ok && rawDefault != nil {
			def := strings.TrimSpace(fmt.Sprintf("%v", rawDefault))
			if def != "" {
				col.Default = &def
			}
		}
		columns = append(columns, col)
	}
	sort.SliceStable(columns, func(a, b int) bool {
		return rowOrdinal(data, columns[a].Name) < rowOrdinal(data, columns[b].Name)
	})
	return columns, nil
}

func (i *IrisDB) GetAllColumns(dbName string) ([]connection.ColumnDefinitionWithTable, error) {
	data, _, err := i.Query(`SELECT * FROM INFORMATION_SCHEMA.COLUMNS`)
	if err != nil {
		return nil, err
	}
	cols := make([]connection.ColumnDefinitionWithTable, 0, len(data))
	for _, row := range data {
		schema := strings.TrimSpace(rowString(row, "TABLE_SCHEMA", "table_schema"))
		table := strings.TrimSpace(rowString(row, "TABLE_NAME", "table_name"))
		name := strings.TrimSpace(rowString(row, "COLUMN_NAME", "column_name"))
		if table == "" || name == "" || isIRISSystemSchema(schema) {
			continue
		}
		tableName := table
		if schema != "" {
			tableName = schema + "." + table
		}
		cols = append(cols, connection.ColumnDefinitionWithTable{
			TableName: tableName,
			Name:      name,
			Type:      buildIRISColumnType(row),
		})
	}
	sort.SliceStable(cols, func(a, b int) bool {
		if cols[a].TableName == cols[b].TableName {
			return cols[a].Name < cols[b].Name
		}
		return cols[a].TableName < cols[b].TableName
	})
	return cols, nil
}

func (i *IrisDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefinition, error) {
	ref, err := parseIRISTableRef(dbName, tableName)
	if err != nil {
		return nil, err
	}
	data, _, err := i.Query(buildIRISInfoSchemaWhereQuery("INFORMATION_SCHEMA.INDEXES", ref))
	if err != nil {
		return nil, err
	}
	indexes := make([]connection.IndexDefinition, 0, len(data))
	for _, row := range data {
		name := strings.TrimSpace(rowString(row, "INDEX_NAME", "index_name", "KEY_NAME", "key_name", "CONSTRAINT_NAME", "constraint_name"))
		column := strings.TrimSpace(rowString(row, "COLUMN_NAME", "column_name"))
		primary, hasPrimaryFlag := irisBoolFromRow(row, "PRIMARY_KEY", "primary_key")
		if name == "" && hasPrimaryFlag && primary {
			name = "PRIMARY"
		}
		if name == "" || column == "" {
			continue
		}
		indexType := normalizeIRISIndexType(rowString(row, "INDEX_TYPE", "index_type", "TYPE", "type"))
		if hasPrimaryFlag && primary {
			indexType = "PRIMARY"
		}
		nonUnique := parseIRISNonUnique(row)
		indexes = append(indexes, connection.IndexDefinition{
			Name:       name,
			ColumnName: column,
			NonUnique:  nonUnique,
			SeqInIndex: parseIRISInt(rowValueAny(row, "ORDINAL_POSITION", "ordinal_position", "SEQ_IN_INDEX", "seq_in_index", "KEY_SEQ", "key_seq")),
			IndexType:  indexType,
		})
	}
	sort.SliceStable(indexes, func(a, b int) bool {
		if indexes[a].Name == indexes[b].Name {
			return indexes[a].SeqInIndex < indexes[b].SeqInIndex
		}
		return indexes[a].Name < indexes[b].Name
	})
	return indexes, nil
}

func (i *IrisDB) GetForeignKeys(dbName, tableName string) ([]connection.ForeignKeyDefinition, error) {
	return []connection.ForeignKeyDefinition{}, nil
}

func (i *IrisDB) GetTriggers(dbName, tableName string) ([]connection.TriggerDefinition, error) {
	return []connection.TriggerDefinition{}, nil
}

func (i *IrisDB) ApplyChanges(tableName string, changes connection.ChangeSet) error {
	if i.conn == nil {
		return fmt.Errorf("连接未打开")
	}
	tx, err := i.conn.Begin()
	if err != nil {
		return err
	}
	defer tx.Rollback()

	for _, keys := range changes.Deletes {
		query, args, ok := buildIRISDeleteSQL(tableName, keys)
		if !ok {
			continue
		}
		res, err := tx.Exec(query, args...)
		if err != nil {
			return fmt.Errorf("删除失败：%v", err)
		}
		if err := requireSingleRowAffected(res, "删除"); err != nil {
			return err
		}
	}

	for _, update := range changes.Updates {
		query, args, ok, err := buildIRISUpdateSQL(tableName, update)
		if err != nil {
			return err
		}
		if !ok {
			continue
		}
		res, err := tx.Exec(query, args...)
		if err != nil {
			return fmt.Errorf("更新失败：%v", err)
		}
		if err := requireSingleRowAffected(res, "更新"); err != nil {
			return err
		}
	}

	for _, row := range changes.Inserts {
		query, args, ok := buildIRISInsertSQL(tableName, row)
		if !ok {
			continue
		}
		res, err := tx.Exec(query, args...)
		if err != nil {
			return fmt.Errorf("插入失败：%v", err)
		}
		if affected, err := res.RowsAffected(); err == nil && affected == 0 {
			return fmt.Errorf("插入未生效：未影响任何行")
		}
	}

	return tx.Commit()
}

func buildIRISInfoSchemaWhereQuery(table string, ref irisTableRef) string {
	conditions := []string{fmt.Sprintf("TABLE_NAME = '%s'", irisSQLLiteral(ref.Table))}
	if ref.Schema != "" {
		conditions = append(conditions, fmt.Sprintf("TABLE_SCHEMA = '%s'", irisSQLLiteral(ref.Schema)))
	}
	orderBy := ""
	switch strings.ToUpper(strings.TrimSpace(table)) {
	case "INFORMATION_SCHEMA.COLUMNS":
		orderBy = " ORDER BY ORDINAL_POSITION"
	case "INFORMATION_SCHEMA.INDEXES":
		orderBy = " ORDER BY INDEX_NAME, ORDINAL_POSITION"
	}
	return fmt.Sprintf("SELECT * FROM %s WHERE %s%s", table, strings.Join(conditions, " AND "), orderBy)
}

func parseIRISTableRef(defaultSchema, raw string) (irisTableRef, error) {
	text := strings.TrimSpace(raw)
	if text == "" {
		return irisTableRef{}, fmt.Errorf("表名不能为空")
	}
	if schemaPart, tablePart, ok := splitIRISTablePath(text); ok {
		schema := cleanIRISIdentifier(schemaPart)
		table := cleanIRISIdentifier(tablePart)
		if table == "" {
			return irisTableRef{}, fmt.Errorf("表名不能为空")
		}
		return irisTableRef{Schema: schema, Table: table}, nil
	}
	return irisTableRef{Schema: cleanIRISIdentifier(defaultSchema), Table: cleanIRISIdentifier(text)}, nil
}

func splitIRISTablePath(raw string) (schemaPart, tablePart string, ok bool) {
	inQuote := false
	for idx := 0; idx < len(raw); idx++ {
		switch raw[idx] {
		case '"':
			if inQuote && idx+1 < len(raw) && raw[idx+1] == '"' {
				idx++
				continue
			}
			inQuote = !inQuote
		case '.':
			if !inQuote {
				return raw[:idx], raw[idx+1:], true
			}
		}
	}
	return "", raw, false
}

func cleanIRISIdentifier(raw string) string {
	text := strings.TrimSpace(raw)
	text = strings.Trim(text, `"`)
	return strings.ReplaceAll(text, `""`, `"`)
}

func irisSQLLiteral(raw string) string {
	return strings.ReplaceAll(raw, "'", "''")
}

func irisQuoteIdent(name string) string {
	text := cleanIRISIdentifier(name)
	text = strings.ReplaceAll(text, `"`, `""`)
	return `"` + text + `"`
}

func irisQuoteTable(raw string) string {
	ref, err := parseIRISTableRef("", raw)
	if err != nil {
		return irisQuoteIdent(raw)
	}
	if ref.Schema != "" {
		return irisQuoteIdent(ref.Schema) + "." + irisQuoteIdent(ref.Table)
	}
	return irisQuoteIdent(ref.Table)
}

func isIRISSystemSchema(schema string) bool {
	normalized := strings.ToUpper(strings.TrimSpace(schema))
	return normalized == "INFORMATION_SCHEMA" ||
		strings.HasPrefix(normalized, "%") ||
		strings.HasPrefix(normalized, "SYS")
}

func rowValue(row map[string]interface{}, keys ...string) (interface{}, bool) {
	for _, key := range keys {
		if value, ok := row[key]; ok {
			return value, true
		}
		for existing, value := range row {
			if strings.EqualFold(existing, key) {
				return value, true
			}
		}
	}
	return nil, false
}

func rowValueAny(row map[string]interface{}, keys ...string) interface{} {
	value, _ := rowValue(row, keys...)
	return value
}

func rowString(row map[string]interface{}, keys ...string) string {
	value, ok := rowValue(row, keys...)
	if !ok || value == nil {
		return ""
	}
	return fmt.Sprintf("%v", value)
}

func parseIRISInt(value interface{}) int {
	switch v := value.(type) {
	case int:
		return v
	case int32:
		return int(v)
	case int64:
		return int(v)
	case float64:
		return int(v)
	case string:
		n, _ := strconv.Atoi(strings.TrimSpace(v))
		return n
	default:
		n, _ := strconv.Atoi(strings.TrimSpace(fmt.Sprintf("%v", value)))
		return n
	}
}

func parseIRISBool(value interface{}) (bool, bool) {
	switch v := value.(type) {
	case bool:
		return v, true
	case int:
		return v != 0, true
	case int64:
		return v != 0, true
	case float64:
		return v != 0, true
	case string:
		switch strings.ToLower(strings.TrimSpace(v)) {
		case "1", "true", "t", "yes", "y":
			return true, true
		case "0", "false", "f", "no", "n":
			return false, true
		}
	}
	return false, false
}

func irisBoolFromRow(row map[string]interface{}, keys ...string) (bool, bool) {
	value, ok := rowValue(row, keys...)
	if !ok {
		return false, false
	}
	return parseIRISBool(value)
}

func parseIRISNonUnique(row map[string]interface{}) int {
	if primary, ok := irisBoolFromRow(row, "PRIMARY_KEY", "primary_key"); ok && primary {
		return 0
	}
	if value, ok := rowValue(row, "NON_UNIQUE", "non_unique"); ok {
		if enabled, ok := parseIRISBool(value); ok {
			if enabled {
				return 1
			}
			return 0
		}
		n := parseIRISInt(value)
		if n != 0 {
			return 1
		}
		return 0
	}
	if value, ok := rowValue(row, "IS_UNIQUE", "is_unique", "UNIQUE", "unique"); ok {
		if unique, ok := parseIRISBool(value); ok && unique {
			return 0
		}
	}
	if unique, ok := irisBoolFromRow(row, "UNIQUE_COLUMN", "unique_column"); ok && unique {
		return 0
	}
	return 1
}

func normalizeIRISIndexType(raw string) string {
	text := strings.ToUpper(strings.TrimSpace(raw))
	if text == "" {
		return "BTREE"
	}
	return text
}

func normalizeIRISNullable(raw string) string {
	switch strings.ToUpper(strings.TrimSpace(raw)) {
	case "NO", "N", "FALSE", "0":
		return "NO"
	default:
		return "YES"
	}
}

func buildIRISColumnType(row map[string]interface{}) string {
	dataType := strings.TrimSpace(rowString(row, "DATA_TYPE", "data_type", "TYPE_NAME", "type_name"))
	if dataType == "" {
		dataType = "VARCHAR"
	}
	upper := strings.ToUpper(dataType)
	charLength := parseIRISInt(rowValueAny(row, "CHARACTER_MAXIMUM_LENGTH", "character_maximum_length", "CHARACTER_MAX_LENGTH", "character_max_length"))
	precision := parseIRISInt(rowValueAny(row, "NUMERIC_PRECISION", "numeric_precision"))
	scale := parseIRISInt(rowValueAny(row, "NUMERIC_SCALE", "numeric_scale"))
	if charLength > 0 && (strings.Contains(upper, "CHAR") || strings.Contains(upper, "VARCHAR")) && !strings.Contains(dataType, "(") {
		return fmt.Sprintf("%s(%d)", dataType, charLength)
	}
	if precision > 0 && (strings.Contains(upper, "NUMERIC") || strings.Contains(upper, "DECIMAL") || strings.Contains(upper, "NUMBER")) && !strings.Contains(dataType, "(") {
		if scale > 0 {
			return fmt.Sprintf("%s(%d,%d)", dataType, precision, scale)
		}
		return fmt.Sprintf("%s(%d)", dataType, precision)
	}
	return dataType
}

func rowOrdinal(rows []map[string]interface{}, columnName string) int {
	for idx, row := range rows {
		if strings.EqualFold(rowString(row, "COLUMN_NAME", "column_name"), columnName) {
			ordinal := parseIRISInt(rowValueAny(row, "ORDINAL_POSITION", "ordinal_position"))
			if ordinal > 0 {
				return ordinal
			}
			return idx + 1
		}
	}
	return len(rows) + 1
}

func irisColumnKeyMap(indexes []connection.IndexDefinition) map[string]string {
	result := map[string]string{}
	for _, idx := range indexes {
		column := strings.TrimSpace(idx.ColumnName)
		if column == "" {
			continue
		}
		if isIRISPrimaryIndex(idx) {
			result[column] = "PRI"
			continue
		}
		if idx.NonUnique == 0 && result[column] == "" {
			result[column] = "UNI"
		}
	}
	return result
}

func isIRISPrimaryIndexName(name string) bool {
	normalized := strings.ToUpper(strings.TrimSpace(name))
	return normalized == "PRIMARY" || normalized == "PRIMARYKEY" || normalized == "IDKEY"
}

func isIRISPrimaryIndex(idx connection.IndexDefinition) bool {
	return isIRISPrimaryIndexName(idx.Name) || strings.EqualFold(strings.TrimSpace(idx.IndexType), "PRIMARY")
}

func buildIRISCreateTableDDL(ref irisTableRef, columns []connection.ColumnDefinition, indexes []connection.IndexDefinition) string {
	qualified := irisQuoteIdent(ref.Table)
	if strings.TrimSpace(ref.Schema) != "" {
		qualified = irisQuoteIdent(ref.Schema) + "." + qualified
	}

	lines := make([]string, 0, len(columns)+1)
	primaryColumns := irisPrimaryColumns(indexes)
	if len(primaryColumns) == 0 {
		primaryColumns = irisPrimaryColumnsFromColumns(columns)
	}
	for _, col := range columns {
		line := fmt.Sprintf("  %s %s", irisQuoteIdent(col.Name), strings.TrimSpace(col.Type))
		if col.Default != nil && strings.TrimSpace(*col.Default) != "" {
			line += " DEFAULT " + strings.TrimSpace(*col.Default)
		}
		if strings.EqualFold(strings.TrimSpace(col.Nullable), "NO") {
			line += " NOT NULL"
		}
		lines = append(lines, line)
	}
	if len(primaryColumns) > 0 {
		lines = append(lines, fmt.Sprintf("  PRIMARY KEY (%s)", irisQuoteIdentList(primaryColumns)))
	}

	var b strings.Builder
	b.WriteString(fmt.Sprintf("CREATE TABLE %s (\n%s\n);", qualified, strings.Join(lines, ",\n")))

	for _, stmt := range buildIRISCreateIndexStatements(ref, indexes) {
		b.WriteString("\n\n")
		b.WriteString(stmt)
	}
	return b.String()
}

func irisPrimaryColumns(indexes []connection.IndexDefinition) []string {
	for _, group := range groupIRISIndexes(indexes) {
		if group.Primary {
			return group.Columns
		}
	}
	return nil
}

func irisPrimaryColumnsFromColumns(columns []connection.ColumnDefinition) []string {
	primaryColumns := make([]string, 0)
	for _, column := range columns {
		if strings.EqualFold(strings.TrimSpace(column.Key), "PRI") && strings.TrimSpace(column.Name) != "" {
			primaryColumns = append(primaryColumns, column.Name)
		}
	}
	return primaryColumns
}

type irisIndexGroup struct {
	Name      string
	Columns   []string
	NonUnique int
	IndexType string
	Primary   bool
}

func groupIRISIndexes(indexes []connection.IndexDefinition) []irisIndexGroup {
	groupsByName := map[string]*irisIndexGroup{}
	order := make([]string, 0)
	for _, idx := range indexes {
		name := strings.TrimSpace(idx.Name)
		column := strings.TrimSpace(idx.ColumnName)
		if name == "" || column == "" {
			continue
		}
		group, ok := groupsByName[name]
		if !ok {
			group = &irisIndexGroup{Name: name, NonUnique: idx.NonUnique, IndexType: idx.IndexType}
			groupsByName[name] = group
			order = append(order, name)
		}
		group.Columns = append(group.Columns, column)
		if idx.NonUnique == 0 {
			group.NonUnique = 0
		}
		if isIRISPrimaryIndex(idx) {
			group.Primary = true
		}
	}
	sort.Strings(order)
	groups := make([]irisIndexGroup, 0, len(order))
	for _, name := range order {
		group := groupsByName[name]
		groups = append(groups, *group)
	}
	return groups
}

func buildIRISCreateIndexStatements(ref irisTableRef, indexes []connection.IndexDefinition) []string {
	qualified := irisQuoteIdent(ref.Table)
	if strings.TrimSpace(ref.Schema) != "" {
		qualified = irisQuoteIdent(ref.Schema) + "." + qualified
	}
	var statements []string
	for _, group := range groupIRISIndexes(indexes) {
		if len(group.Columns) == 0 || group.Primary {
			continue
		}
		unique := ""
		if group.NonUnique == 0 {
			unique = "UNIQUE "
		}
		statements = append(statements, fmt.Sprintf("CREATE %sINDEX %s ON %s (%s);", unique, irisQuoteIdent(group.Name), qualified, irisQuoteIdentList(group.Columns)))
	}
	return statements
}

func irisQuoteIdentList(columns []string) string {
	quoted := make([]string, 0, len(columns))
	for _, column := range columns {
		quoted = append(quoted, irisQuoteIdent(column))
	}
	return strings.Join(quoted, ", ")
}

func buildIRISDeleteSQL(tableName string, keys map[string]interface{}) (string, []interface{}, bool) {
	wheres, args := irisAssignments(keys, " = ?")
	if len(wheres) == 0 {
		return "", nil, false
	}
	return fmt.Sprintf("DELETE FROM %s WHERE %s", irisQuoteTable(tableName), strings.Join(wheres, " AND ")), args, true
}

func buildIRISUpdateSQL(tableName string, update connection.UpdateRow) (string, []interface{}, bool, error) {
	sets, args := irisAssignments(update.Values, " = ?")
	if len(sets) == 0 {
		return "", nil, false, nil
	}
	wheres, whereArgs := irisAssignments(update.Keys, " = ?")
	if len(wheres) == 0 {
		return "", nil, false, fmt.Errorf("更新操作需要主键条件")
	}
	args = append(args, whereArgs...)
	return fmt.Sprintf("UPDATE %s SET %s WHERE %s", irisQuoteTable(tableName), strings.Join(sets, ", "), strings.Join(wheres, " AND ")), args, true, nil
}

func buildIRISInsertSQL(tableName string, row map[string]interface{}) (string, []interface{}, bool) {
	if len(row) == 0 {
		return "", nil, false
	}
	keys := sortedMapKeys(row)
	cols := make([]string, 0, len(keys))
	placeholders := make([]string, 0, len(keys))
	args := make([]interface{}, 0, len(keys))
	for _, key := range keys {
		cols = append(cols, irisQuoteIdent(key))
		placeholders = append(placeholders, "?")
		args = append(args, row[key])
	}
	return fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", irisQuoteTable(tableName), strings.Join(cols, ", "), strings.Join(placeholders, ", ")), args, true
}

func irisAssignments(values map[string]interface{}, suffix string) ([]string, []interface{}) {
	keys := sortedMapKeys(values)
	parts := make([]string, 0, len(keys))
	args := make([]interface{}, 0, len(keys))
	for _, key := range keys {
		parts = append(parts, irisQuoteIdent(key)+suffix)
		args = append(args, values[key])
	}
	return parts, args
}

func sortedMapKeys(values map[string]interface{}) []string {
	keys := make([]string, 0, len(values))
	for key := range values {
		if strings.TrimSpace(key) != "" {
			keys = append(keys, key)
		}
	}
	sort.Strings(keys)
	return keys
}
