package repository

import (
	"context"
	"fmt"
	"sync/atomic"
	"testing"

	gormsqlite "gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
	_ "modernc.org/sqlite"

	"github.com/user-management-system/internal/domain"
)

var socialAccountTestCounter int64

func openSocialAccountTestDB(t *testing.T) *gorm.DB {
	t.Helper()

	id := atomic.AddInt64(&socialAccountTestCounter, 1)
	dsn := fmt.Sprintf("file:socialaccounttestdb%d?mode=memory&cache=private", id)

	db, err := gorm.Open(gormsqlite.New(gormsqlite.Config{
		DriverName: "sqlite",
		DSN:        dsn,
	}), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		t.Fatalf("打开测试数据库失败: %v", err)
	}

	if err := db.AutoMigrate(&domain.SocialAccount{}); err != nil {
		t.Fatalf("数据库迁移失败: %v", err)
	}
	return db
}

func setupSocialAccountTestDB(t *testing.T) *gorm.DB {
	return openSocialAccountTestDB(t)
}

func TestSocialAccountRepository_Create(t *testing.T) {
	db := setupSocialAccountTestDB(t)
	repo, err := NewSocialAccountRepository(db)
	if err != nil {
		t.Fatalf("NewSocialAccountRepository() error = %v", err)
	}
	ctx := context.Background()

	account := &domain.SocialAccount{
		UserID:   1,
		Provider: "github",
		OpenID:   "openid-123",
		Nickname: "testuser",
		Status:   domain.SocialAccountStatusActive,
	}

	if err := repo.Create(ctx, account); err != nil {
		t.Fatalf("Create() error = %v", err)
	}
	if account.ID == 0 {
		t.Error("创建后账户ID不应为0")
	}
}

func TestSocialAccountRepository_GetByID(t *testing.T) {
	db := setupSocialAccountTestDB(t)
	repo, err := NewSocialAccountRepository(db)
	if err != nil {
		t.Fatalf("NewSocialAccountRepository() error = %v", err)
	}
	ctx := context.Background()

	account := &domain.SocialAccount{
		UserID:   1,
		Provider: "github",
		OpenID:   "openid-getbyid",
		Nickname: "getbyid-user",
		Status:   domain.SocialAccountStatusActive,
	}
	repo.Create(ctx, account)

	found, err := repo.GetByID(ctx, account.ID)
	if err != nil {
		t.Fatalf("GetByID() error = %v", err)
	}
	if found.Nickname != "getbyid-user" {
		t.Errorf("Nickname = %v, want getbyid-user", found.Nickname)
	}
}

func TestSocialAccountRepository_GetByUserID(t *testing.T) {
	db := setupSocialAccountTestDB(t)
	repo, err := NewSocialAccountRepository(db)
	if err != nil {
		t.Fatalf("NewSocialAccountRepository() error = %v", err)
	}
	ctx := context.Background()

	repo.Create(ctx, &domain.SocialAccount{
		UserID:   1,
		Provider: "github",
		OpenID:   "openid-user1-1",
		Status:   domain.SocialAccountStatusActive,
	})
	repo.Create(ctx, &domain.SocialAccount{
		UserID:   1,
		Provider: "wechat",
		OpenID:   "openid-user1-2",
		Status:   domain.SocialAccountStatusActive,
	})
	repo.Create(ctx, &domain.SocialAccount{
		UserID:   2,
		Provider: "github",
		OpenID:   "openid-user2",
		Status:   domain.SocialAccountStatusActive,
	})

	accounts, err := repo.GetByUserID(ctx, 1)
	if err != nil {
		t.Fatalf("GetByUserID() error = %v", err)
	}
	if len(accounts) != 2 {
		t.Errorf("len(accounts) = %d, want 2", len(accounts))
	}
}

func TestSocialAccountRepository_GetByProviderAndOpenID(t *testing.T) {
	db := setupSocialAccountTestDB(t)
	repo, err := NewSocialAccountRepository(db)
	if err != nil {
		t.Fatalf("NewSocialAccountRepository() error = %v", err)
	}
	ctx := context.Background()

	account := &domain.SocialAccount{
		UserID:   1,
		Provider: "github",
		OpenID:   "unique-openid-123",
		Nickname: "github-user",
		Status:   domain.SocialAccountStatusActive,
	}
	repo.Create(ctx, account)

	found, err := repo.GetByProviderAndOpenID(ctx, "github", "unique-openid-123")
	if err != nil {
		t.Fatalf("GetByProviderAndOpenID() error = %v", err)
	}
	if found.UserID != 1 {
		t.Errorf("UserID = %d, want 1", found.UserID)
	}
}

func TestSocialAccountRepository_Update(t *testing.T) {
	db := setupSocialAccountTestDB(t)
	repo, err := NewSocialAccountRepository(db)
	if err != nil {
		t.Fatalf("NewSocialAccountRepository() error = %v", err)
	}
	ctx := context.Background()

	account := &domain.SocialAccount{
		UserID:   1,
		Provider: "github",
		OpenID:   "openid-update",
		Nickname: "before-update",
		Status:   domain.SocialAccountStatusActive,
	}
	repo.Create(ctx, account)

	account.Nickname = "after-update"
	if err := repo.Update(ctx, account); err != nil {
		t.Fatalf("Update() error = %v", err)
	}

	found, _ := repo.GetByID(ctx, account.ID)
	if found.Nickname != "after-update" {
		t.Errorf("Nickname = %v, want after-update", found.Nickname)
	}
}

func TestSocialAccountRepository_Update_DoesNotRewriteBindingIdentityFields(t *testing.T) {
	db := setupSocialAccountTestDB(t)
	repo, err := NewSocialAccountRepository(db)
	if err != nil {
		t.Fatalf("NewSocialAccountRepository() error = %v", err)
	}
	ctx := context.Background()

	account := &domain.SocialAccount{
		UserID:   1,
		Provider: "github",
		OpenID:   "openid-identity",
		Nickname: "before-update",
		Status:   domain.SocialAccountStatusActive,
	}
	if err := repo.Create(ctx, account); err != nil {
		t.Fatalf("Create() error = %v", err)
	}

	account.UserID = 999
	account.Provider = "wechat"
	account.OpenID = "rewritten-openid"
	account.Nickname = "after-update"
	if err := repo.Update(ctx, account); err != nil {
		t.Fatalf("Update() error = %v", err)
	}

	found, err := repo.GetByID(ctx, account.ID)
	if err != nil {
		t.Fatalf("GetByID() error = %v", err)
	}
	if found == nil {
		t.Fatal("expected social account after update")
	}
	if found.UserID != 1 {
		t.Fatalf("UserID = %d, want 1", found.UserID)
	}
	if found.Provider != "github" {
		t.Fatalf("Provider = %q, want github", found.Provider)
	}
	if found.OpenID != "openid-identity" {
		t.Fatalf("OpenID = %q, want openid-identity", found.OpenID)
	}
	if found.Nickname != "after-update" {
		t.Fatalf("Nickname = %q, want after-update", found.Nickname)
	}
}

func TestSocialAccountRepository_Delete(t *testing.T) {
	db := setupSocialAccountTestDB(t)
	repo, err := NewSocialAccountRepository(db)
	if err != nil {
		t.Fatalf("NewSocialAccountRepository() error = %v", err)
	}
	ctx := context.Background()

	account := &domain.SocialAccount{
		UserID:   1,
		Provider: "github",
		OpenID:   "openid-delete",
		Status:   domain.SocialAccountStatusActive,
	}
	repo.Create(ctx, account)

	if err := repo.Delete(ctx, account.ID); err != nil {
		t.Fatalf("Delete() error = %v", err)
	}
}

func TestSocialAccountRepository_DeleteByProviderAndUserID(t *testing.T) {
	db := setupSocialAccountTestDB(t)
	repo, err := NewSocialAccountRepository(db)
	if err != nil {
		t.Fatalf("NewSocialAccountRepository() error = %v", err)
	}
	ctx := context.Background()

	repo.Create(ctx, &domain.SocialAccount{
		UserID:   1,
		Provider: "github",
		OpenID:   "openid-del-provider",
		Status:   domain.SocialAccountStatusActive,
	})

	err = repo.DeleteByProviderAndUserID(ctx, "github", 1)
	if err != nil {
		t.Fatalf("DeleteByProviderAndUserID() error = %v", err)
	}

	accounts, _ := repo.GetByUserID(ctx, 1)
	if len(accounts) != 0 {
		t.Errorf("len(accounts) = %d, want 0 after delete", len(accounts))
	}
}

func TestSocialAccountRepository_List(t *testing.T) {
	db := setupSocialAccountTestDB(t)
	repo, err := NewSocialAccountRepository(db)
	if err != nil {
		t.Fatalf("NewSocialAccountRepository() error = %v", err)
	}
	ctx := context.Background()

	repo.Create(ctx, &domain.SocialAccount{
		UserID:   1,
		Provider: "github",
		OpenID:   "openid-list-1",
		Status:   domain.SocialAccountStatusActive,
	})
	repo.Create(ctx, &domain.SocialAccount{
		UserID:   2,
		Provider: "wechat",
		OpenID:   "openid-list-2",
		Status:   domain.SocialAccountStatusActive,
	})

	accounts, total, err := repo.List(ctx, 0, 10)
	if err != nil {
		t.Fatalf("List() error = %v", err)
	}
	if len(accounts) != 2 {
		t.Errorf("len(accounts) = %d, want 2", len(accounts))
	}
	if total != 2 {
		t.Errorf("total = %d, want 2", total)
	}
}
