package crud

import (
	"context"
	"fmt"
	"sync"
	"time"

	"aijianzhan/platform/internal/meta"

	"github.com/google/uuid"
)

// MemoryEngine 骨架默认引擎：不依赖外部 DB，方便本地验证动态 CRUD。
type MemoryEngine struct {
	mu   sync.RWMutex
	rows map[string][]map[string]any // schema.table
}

func NewMemoryEngine() *MemoryEngine {
	return &MemoryEngine{rows: map[string][]map[string]any{}}
}

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

func (e *MemoryEngine) List(ctx context.Context, ref *meta.ResourceRef, tenantID int64, page, pageSize int, filters map[string]string, sortBy string) ([]map[string]any, int, error) {
	if err := ensureOp(ref, "list"); err != nil {
		return nil, 0, err
	}
	if err := validateFilters(ref, filters); err != nil {
		return nil, 0, err
	}
	if err := validateSort(ref, sortBy); err != nil {
		return nil, 0, err
	}
	page, pageSize = pageBounds(ref, page, pageSize)

	e.mu.RLock()
	defer e.mu.RUnlock()
	all := e.rows[tableKey(ref)]
	filtered := make([]map[string]any, 0, len(all))
	for _, row := range all {
		if toInt64(row["tenant_id"]) != tenantID {
			continue
		}
		if !matchOrgScope(row, RowScopeFrom(ctx).OrgUnitIDs) {
			continue
		}
		if matchFilters(row, filters) {
			filtered = append(filtered, cloneRow(row))
		}
	}
	sortRows(filtered, sortBy)
	total := len(filtered)
	start := (page - 1) * pageSize
	if start >= total {
		return []map[string]any{}, total, nil
	}
	end := start + pageSize
	if end > total {
		end = total
	}
	return filtered[start:end], total, nil
}

func (e *MemoryEngine) Get(ctx context.Context, ref *meta.ResourceRef, tenantID int64, id string) (map[string]any, error) {
	if err := ensureOp(ref, "get"); err != nil {
		return nil, err
	}
	e.mu.RLock()
	defer e.mu.RUnlock()
	pk := ref.Entity.PrimaryKey
	for _, row := range e.rows[tableKey(ref)] {
		if toInt64(row["tenant_id"]) == tenantID && fmt.Sprint(row[pk]) == id {
			if !matchOrgScope(row, RowScopeFrom(ctx).OrgUnitIDs) {
				break
			}
			return cloneRow(row), nil
		}
	}
	return nil, fmt.Errorf("not found")
}

func (e *MemoryEngine) Create(ctx context.Context, ref *meta.ResourceRef, tenantID, userID int64, body map[string]any) (map[string]any, error) {
	if err := ensureOp(ref, "create"); err != nil {
		return nil, err
	}
	row, err := sanitizeBody(ref.Entity, body, false)
	if err != nil {
		return nil, err
	}
	pk := ref.Entity.PrimaryKey
	if _, ok := row[pk]; !ok {
		if isAutoPK(ref.Entity) {
			row[pk] = time.Now().UnixNano()
		} else {
			row[pk] = uuid.NewString()
		}
	}
	now := time.Now().UTC().Format(time.RFC3339)
	row["tenant_id"] = tenantID
	row["created_by"] = userID
	row["created_at"] = now
	row["updated_at"] = now
	if scope := RowScopeFrom(ctx); scope.WriteOrgUnit > 0 {
		row["org_unit_id"] = scope.WriteOrgUnit
	}

	e.mu.Lock()
	defer e.mu.Unlock()
	k := tableKey(ref)
	e.rows[k] = append(e.rows[k], row)
	return cloneRow(row), nil
}

func (e *MemoryEngine) Update(_ context.Context, ref *meta.ResourceRef, tenantID int64, id string, body map[string]any) (map[string]any, error) {
	if err := ensureOp(ref, "update"); err != nil {
		return nil, err
	}
	patch, err := sanitizeBody(ref.Entity, body, true)
	if err != nil {
		return nil, err
	}
	e.mu.Lock()
	defer e.mu.Unlock()
	pk := ref.Entity.PrimaryKey
	rows := e.rows[tableKey(ref)]
	for i, row := range rows {
		if toInt64(row["tenant_id"]) == tenantID && fmt.Sprint(row[pk]) == id {
			for k, v := range patch {
				if k == pk || k == "tenant_id" {
					continue
				}
				row[k] = v
			}
			row["updated_at"] = time.Now().UTC().Format(time.RFC3339)
			rows[i] = row
			return cloneRow(row), nil
		}
	}
	return nil, fmt.Errorf("not found")
}

func (e *MemoryEngine) Delete(_ context.Context, ref *meta.ResourceRef, tenantID int64, id string) error {
	if err := ensureOp(ref, "delete"); err != nil {
		return err
	}
	e.mu.Lock()
	defer e.mu.Unlock()
	pk := ref.Entity.PrimaryKey
	k := tableKey(ref)
	rows := e.rows[k]
	out := rows[:0]
	found := false
	for _, row := range rows {
		if toInt64(row["tenant_id"]) == tenantID && fmt.Sprint(row[pk]) == id {
			found = true
			continue
		}
		out = append(out, row)
	}
	if !found {
		return fmt.Errorf("not found")
	}
	e.rows[k] = out
	return nil
}
