package memory

import (
	"context"
	"os"
	"testing"

	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// TestBaseModelID 验证雪花 ID 规约：NewID 非空且不重复；BeforeCreate 补 ID、已有则保留。
func TestBaseModelID(t *testing.T) {
	a, b := NewID(), NewID()
	if a == "" || b == "" {
		t.Fatal("NewID 不应为空")
	}
	if a == b {
		t.Errorf("两次 NewID 应不同: %s", a)
	}
	var m BaseModel
	if err := m.BeforeCreate(nil); err != nil {
		t.Fatal(err)
	}
	if m.ID == "" {
		t.Error("BeforeCreate 应补 ID")
	}
	m.ID = "fixed"
	_ = m.BeforeCreate(nil)
	if m.ID != "fixed" {
		t.Error("BeforeCreate 不应覆盖已有 ID")
	}
}

// TestProfileStore_Integration 跑真实 Postgres（设 MEMORY_TEST_DSN 才执行）：
// 验证迁移后表含雪花规约字段、Upsert (user_id,key) 冲突覆盖而非新增、Get 渲染。
func TestProfileStore_Integration(t *testing.T) {
	dsn := os.Getenv("MEMORY_TEST_DSN")
	if dsn == "" {
		t.Skip("设 MEMORY_TEST_DSN 启用 Postgres 集成测试")
	}
	raw, err := gorm.Open(postgres.New(postgres.Config{DSN: dsn}), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
	if err != nil {
		t.Fatalf("连接 Postgres: %v", err)
	}
	_ = raw.Migrator().DropTable("sundynix_user_profile") // 干净起点

	s := Open(dsn) // 触发迁移 + AutoMigrate
	if s.db == nil {
		t.Fatal("Store 不应降级")
	}
	ctx := context.Background()

	// 新规约字段齐全。
	for _, col := range []string{"id", "created_at", "updated_at", "deleted_at"} {
		if !raw.Migrator().HasColumn(&Profile{}, col) {
			t.Errorf("表应含规约字段 %s", col)
		}
	}

	// Upsert 两次同 (user_id,key) → 覆盖，不新增；id 稳定；importance/last_seen 写入。
	if err := s.Upsert(ctx, "u1", "城市", "北京", 5); err != nil {
		t.Fatal(err)
	}
	var first Profile
	raw.Where("user_id = ? AND key = ?", "u1", "城市").First(&first)
	if first.ID == "" || first.CreatedAt.IsZero() {
		t.Error("行应有雪花 id 与创建时间")
	}
	if first.Importance != 5 || first.LastSeenAt.IsZero() {
		t.Errorf("应写入 importance 与 last_seen: imp=%v last=%v", first.Importance, first.LastSeenAt)
	}
	if err := s.Upsert(ctx, "u1", "城市", "上海", 8); err != nil {
		t.Fatal(err)
	}
	var cnt int64
	raw.Model(&Profile{}).Where("user_id = ?", "u1").Count(&cnt)
	if cnt != 1 {
		t.Errorf("同键 upsert 应覆盖，期望 1 行，得 %d", cnt)
	}
	var after Profile
	raw.Where("user_id = ? AND key = ?", "u1", "城市").First(&after)
	if after.Value != "上海" || after.ID != first.ID || after.Importance != 8 {
		t.Errorf("应覆盖 value/importance 且保留 id: value=%s imp=%v id=%s/%s", after.Value, after.Importance, after.ID, first.ID)
	}

	// Delete 软删：行打 deleted_at，正常查询不返回，但物理行还在（可审计）。
	_ = s.Upsert(ctx, "u1", "临时", "可删", 1)
	if err := s.Delete(ctx, "u1", "临时"); err != nil {
		t.Fatal(err)
	}
	var liveCnt, rawCnt int64
	raw.Model(&Profile{}).Where("user_id = ? AND key = ?", "u1", "临时").Count(&liveCnt)
	raw.Unscoped().Model(&Profile{}).Where("user_id = ? AND key = ?", "u1", "临时").Count(&rawCnt)
	if liveCnt != 0 || rawCnt != 1 {
		t.Errorf("软删后正常查询应 0、物理行应 1：live=%d raw=%d", liveCnt, rawCnt)
	}

	// Get 渲染多行（按 key 排序），软删的不出现。
	_ = s.Upsert(ctx, "u1", "爱好", "围棋", 6)
	got, _ := s.Get(ctx, "u1")
	if got == "" || got != "- 城市：上海\n- 爱好：围棋" {
		t.Errorf("Get 渲染不符: %q", got)
	}
}

// TestProfileStore_LegacyMigration 验证旧复合主键表（无 id）→ 雪花规约表的迁移保留数据。
func TestProfileStore_LegacyMigration(t *testing.T) {
	dsn := os.Getenv("MEMORY_TEST_DSN")
	if dsn == "" {
		t.Skip("设 MEMORY_TEST_DSN 启用 Postgres 集成测试")
	}
	raw, err := gorm.Open(postgres.New(postgres.Config{DSN: dsn}), &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)})
	if err != nil {
		t.Fatalf("连接 Postgres: %v", err)
	}
	// 造一个旧 schema 表（复合主键、无 id/时间戳）并塞一条偏好。
	raw.Migrator().DropTable("sundynix_user_profile")
	if err := raw.Exec(`CREATE TABLE sundynix_user_profile (user_id varchar(64), "key" varchar(64), value text, PRIMARY KEY(user_id, "key"))`).Error; err != nil {
		t.Fatalf("建旧表: %v", err)
	}
	raw.Exec(`INSERT INTO sundynix_user_profile (user_id, "key", value) VALUES ('legacy', '语言', 'Go')`)

	s := Open(dsn) // 应触发 migrateLegacyProfile：备份→重建→回灌
	if s.db == nil {
		t.Fatal("Store 不应降级")
	}
	if !raw.Migrator().HasColumn(&Profile{}, "id") {
		t.Error("迁移后应有 id 列")
	}
	var p Profile
	if err := raw.Where("user_id = ? AND key = ?", "legacy", "语言").First(&p).Error; err != nil {
		t.Fatalf("迁移后旧数据应保留: %v", err)
	}
	if p.Value != "Go" || p.ID == "" {
		t.Errorf("旧偏好应保留且补新 id: value=%s id=%s", p.Value, p.ID)
	}
}
