package crud

import (
	"fmt"
	"sort"
	"strconv"
	"strings"

	"aijianzhan/platform/internal/blueprint"
	"aijianzhan/platform/internal/meta"
)

func ensureOp(ref *meta.ResourceRef, op string) error {
	for _, o := range ref.Resource.Operations {
		if o == op {
			return nil
		}
	}
	return fmt.Errorf("operation %s not allowed", op)
}

func validateFilters(ref *meta.ResourceRef, filters map[string]string) error {
	if len(filters) == 0 {
		return nil
	}
	allowed := map[string]struct{}{}
	if ref.Resource.List != nil {
		for _, f := range ref.Resource.List.AllowedFilters {
			allowed[f] = struct{}{}
		}
	}
	for k := range filters {
		if _, ok := allowed[k]; !ok {
			return fmt.Errorf("filter not allowed: %s", k)
		}
	}
	return nil
}

func validateSort(ref *meta.ResourceRef, sortBy string) error {
	if sortBy == "" {
		return nil
	}
	field := strings.TrimPrefix(sortBy, "-")
	allowed := map[string]struct{}{}
	if ref.Resource.List != nil {
		for _, s := range ref.Resource.List.AllowedSorts {
			allowed[s] = struct{}{}
		}
	}
	if _, ok := allowed[field]; !ok {
		return fmt.Errorf("sort not allowed: %s", field)
	}
	return nil
}

func sanitizeBody(entity blueprint.Entity, body map[string]any, partial bool) (map[string]any, error) {
	fields := map[string]blueprint.Field{}
	for _, f := range entity.Fields {
		fields[f.Name] = f
	}
	out := map[string]any{}
	for k, v := range body {
		if k == "tenant_id" || k == "org_unit_id" || k == "created_at" || k == "updated_at" || k == "created_by" {
			continue
		}
		f, ok := fields[k]
		if !ok {
			return nil, fmt.Errorf("unknown field: %s", k)
		}
		if f.Name == entity.PrimaryKey && partial {
			continue
		}
		out[k] = v
	}
	if !partial {
		system := map[string]struct{}{
			"tenant_id": {}, "org_unit_id": {}, "created_at": {}, "updated_at": {}, "created_by": {},
		}
		for _, f := range entity.Fields {
			if f.Name == entity.PrimaryKey {
				continue
			}
			if _, ok := system[f.Name]; ok {
				continue
			}
			if _, ok := out[f.Name]; !ok && !blueprint.BoolOr(f.Nullable, true) && f.Default == nil {
				return nil, fmt.Errorf("missing required field: %s", f.Name)
			}
		}
	}
	return out, nil
}

func fieldByName(entity blueprint.Entity, name string) (blueprint.Field, bool) {
	for _, f := range entity.Fields {
		if f.Name == name {
			return f, true
		}
	}
	return blueprint.Field{}, false
}

func isAutoPK(entity blueprint.Entity) bool {
	f, ok := fieldByName(entity, entity.PrimaryKey)
	if !ok {
		return false
	}
	return f.Type == "bigint" || f.Type == "int"
}

func quoteIdent(name string) string {
	return `"` + strings.ReplaceAll(name, `"`, ``) + `"`
}

func qualifiedTable(ref *meta.ResourceRef) string {
	return quoteIdent(ref.App.SchemaName) + "." + quoteIdent(ref.Entity.Table)
}

func pageBounds(ref *meta.ResourceRef, page, pageSize int) (int, int) {
	if page <= 0 {
		page = 1
	}
	if pageSize <= 0 {
		pageSize = 20
	}
	if ref.Resource.List != nil && ref.Resource.List.MaxPageSize > 0 && pageSize > ref.Resource.List.MaxPageSize {
		pageSize = ref.Resource.List.MaxPageSize
	}
	return page, pageSize
}

func matchFilters(row map[string]any, filters map[string]string) bool {
	for k, v := range filters {
		if fmt.Sprint(row[k]) != v {
			return false
		}
	}
	return true
}

func sortRows(rows []map[string]any, sortBy string) {
	if sortBy == "" {
		return
	}
	desc := strings.HasPrefix(sortBy, "-")
	field := strings.TrimPrefix(sortBy, "-")
	sort.SliceStable(rows, func(i, j int) bool {
		a, b := fmt.Sprint(rows[i][field]), fmt.Sprint(rows[j][field])
		if desc {
			return a > b
		}
		return a < b
	})
}

func cloneRow(in map[string]any) map[string]any {
	out := make(map[string]any, len(in))
	for k, v := range in {
		out[k] = v
	}
	return out
}

func toInt64(v any) int64 {
	switch t := v.(type) {
	case int64:
		return t
	case int:
		return int64(t)
	case float64:
		return int64(t)
	case string:
		n, _ := strconv.ParseInt(t, 10, 64)
		return n
	default:
		return 0
	}
}

func normalizeDBValue(v any) any {
	switch t := v.(type) {
	case []byte:
		return string(t)
	default:
		return t
	}
}
