package main

import (
	"fmt"
	"io"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"unicode"

	appErrors "github.com/nexuscrm/backend/pkg/errors"
)

// syncFieldTypesJSON copies fieldTypes.json from shared/constants to backend/pkg/fieldtypes
// This is needed because Go's //go:embed cannot follow symlinks.
func syncFieldTypesJSON(projectRoot string) error {
	src := filepath.Join(projectRoot, "shared", "constants", "fieldTypes.json")
	dst := filepath.Join(projectRoot, "backend", "pkg", "fieldtypes", "fieldTypes.json")

	srcFile, err := os.Open(src)
	if err != nil {
		return appErrors.NewInternalError("open source file", err)
	}
	defer srcFile.Close()

	dstFile, err := os.Create(dst)
	if err != nil {
		return appErrors.NewInternalError("create destination file", err)
	}
	defer dstFile.Close()

	if _, err := io.Copy(dstFile, srcFile); err != nil {
		return appErrors.NewInternalError("copy file", err)
	}

	fmt.Printf("✅ Synced: %s\n", dst)
	return nil
}

// tableNameToConstant converts "_System_ApprovalWorkItem" to "TableApprovalWorkItem"
// and "my_table" to "TableMyTable"
func tableNameToConstant(name string) string {
	clean := strings.TrimPrefix(name, "_System_")
	clean = strings.TrimPrefix(clean, "_")
	// Table names are already PascalCase (e.g., ApprovalWorkItem), just prefix with Table
	return "Table" + snakeToPascal(clean)
}

// tableNameToPrefix converts "_System_User" to "SysUser" and "account" to "Account"
func tableNameToPrefix(name string) string {
	if strings.HasPrefix(name, "_System_") {
		clean := strings.TrimPrefix(name, "_System_")
		return "Sys" + snakeToPascal(clean)
	}
	// For business objects (custom), just use PascalCase name
	return snakeToPascal(name)
}

// tableNameToStructName converts "_System_User" to "SystemUser" and "account" to "Account"
func tableNameToStructName(name string) string {
	if strings.HasPrefix(name, "_System_") {
		clean := strings.TrimPrefix(name, "_")
		clean = strings.ReplaceAll(clean, "_", "")
		// Handle special cases
		clean = strings.ReplaceAll(clean, "System", "System")
		return clean
	}
	return snakeToPascal(name)
}

// tableNameToTSKey converts "_System_User" to "SYSTEM_USER" and "account" to "ACCOUNT"
func tableNameToTSKey(name string) string {
	clean := strings.TrimPrefix(name, "_")
	clean = strings.ReplaceAll(clean, "_", "_")
	return strings.ToUpper(clean)
}

func snakeToPascal(s string) string {

	parts := strings.Split(s, "_")
	for i, p := range parts {
		if len(p) > 0 {
			parts[i] = strings.ToUpper(p[:1]) + p[1:]
		}
	}
	result := strings.Join(parts, "")

	// Handle common abbreviations
	result = strings.ReplaceAll(result, "Id", "ID")
	result = strings.ReplaceAll(result, "Api", "API")
	result = strings.ReplaceAll(result, "Ui", "UI")
	result = strings.ReplaceAll(result, "Url", "URL")
	result = strings.ReplaceAll(result, "Html", "HTML")
	result = strings.ReplaceAll(result, "Json", "JSON")
	result = strings.ReplaceAll(result, "Sql", "SQL")
	result = strings.ReplaceAll(result, "Ip", "IP")

	return result
}

// pascalToSnake converts "CreatedDate" to "created_date" and "ID" to "id"
func pascalToSnake(s string) string {
	// Handle special acronyms
	s = strings.ReplaceAll(s, "ID", "Id")
	s = strings.ReplaceAll(s, "API", "Api")
	s = strings.ReplaceAll(s, "UI", "Ui")
	s = strings.ReplaceAll(s, "URL", "Url")
	s = strings.ReplaceAll(s, "IP", "Ip")
	s = strings.ReplaceAll(s, "JSON", "Json")
	s = strings.ReplaceAll(s, "HTML", "Html")

	var result strings.Builder
	for i, r := range s {
		if unicode.IsUpper(r) && i > 0 {
			result.WriteRune('_')
		}
		result.WriteRune(unicode.ToLower(r))
	}
	return result.String()
}

func dedupeColumns(cols []ColumnDef, keyFn func(ColumnDef) string) []ColumnDef {
	deduped := make([]ColumnDef, 0, len(cols))
	seen := make(map[string]struct{}, len(cols))
	for _, col := range cols {
		key := keyFn(col)
		if _, exists := seen[key]; exists {
			continue
		}
		seen[key] = struct{}{}
		deduped = append(deduped, col)
	}
	return deduped
}

// sqlTypeToGoType converts SQL types to Go types
func sqlTypeToGoType(sqlType string, nullable bool) string {
	sqlType = strings.ToUpper(sqlType)

	// Handle ENUM types
	if strings.HasPrefix(sqlType, "ENUM") {
		if nullable {
			return "*string"
		}
		return "string"
	}

	// Extract base type (e.g., VARCHAR(255) -> VARCHAR)
	baseType := regexp.MustCompile(`\([^)]*\)`).ReplaceAllString(sqlType, "")
	baseType = strings.TrimSpace(baseType)

	var goType string
	switch baseType {
	case "VARCHAR", "TEXT", "CHAR", "LONGTEXT", "MEDIUMTEXT":
		goType = "string"
	case "INT", "INTEGER", "SMALLINT", "MEDIUMINT":
		goType = "int"
	case "BIGINT":
		goType = "int64"
	case "TINYINT":
		// TINYINT(1) is typically boolean
		if strings.Contains(sqlType, "(1)") {
			goType = "bool"
		} else {
			goType = "int8"
		}
	case "DECIMAL", "NUMERIC", "FLOAT", "DOUBLE":
		goType = "float64"
	case "DATETIME", "TIMESTAMP", "DATE", "TIME":
		goType = "time.Time"
	case "BOOLEAN", "BOOL":
		goType = "bool"
	case "JSON":
		goType = "json.RawMessage"
	case "BLOB", "LONGBLOB", "MEDIUMBLOB":
		goType = "[]byte"
	default:
		goType = "string" // fallback
	}

	// Handle nullable types (use pointers)
	if nullable && goType != "bool" && goType != "json.RawMessage" && goType != "[]byte" {
		return "*" + goType
	}

	return goType
}

// sqlTypeToTSType converts SQL types to TypeScript types
func sqlTypeToTSType(sqlType string) string {
	sqlType = strings.ToUpper(sqlType)

	// Handle ENUM types
	if strings.HasPrefix(sqlType, "ENUM") {
		return "string"
	}

	baseType := regexp.MustCompile(`\([^)]*\)`).ReplaceAllString(sqlType, "")
	baseType = strings.TrimSpace(baseType)

	switch baseType {
	case "VARCHAR", "TEXT", "CHAR", "LONGTEXT", "MEDIUMTEXT":
		return "string"
	case "INT", "INTEGER", "SMALLINT", "MEDIUMINT", "BIGINT", "TINYINT":
		// TINYINT(1) is typically boolean
		if strings.Contains(sqlType, "(1)") && baseType == "TINYINT" {
			return "boolean"
		}
		return "number"
	case "DECIMAL", "NUMERIC", "FLOAT", "DOUBLE":
		return "number"
	case "DATETIME", "TIMESTAMP", "DATE", "TIME":
		return "string" // ISO date strings in JSON
	case "BOOLEAN", "BOOL":
		return "boolean"
	case "JSON":
		return "Record<string, unknown>"
	default:
		return "unknown"
	}
}
