package user_test

import (
	"context"
	"strings"
	"testing"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/yaoapp/gou/model"
	"github.com/yaoapp/kun/maps"
)

// TestTeamData represents test team data structure
type TestTeamData struct {
	Name        string                 `json:"name"`
	DisplayName string                 `json:"display_name"`
	Description string                 `json:"description"`
	Website     string                 `json:"website"`
	OwnerID     string                 `json:"owner_id"`
	Status      string                 `json:"status"`
	Type        string                 `json:"type"`
	TypeID      string                 `json:"type_id"`
	Metadata    map[string]interface{} `json:"metadata"`
}

// TestMemberData represents test member data structure
type TestMemberData struct {
	TeamID     string `json:"team_id"`
	UserID     string `json:"user_id"`
	RoleID     string `json:"role_id"`
	Status     string `json:"status"`
	InvitedBy  string `json:"invited_by"`
	MemberType string `json:"member_type"`
}

func TestTeamBasicOperations(t *testing.T) {
	prepare(t)
	defer clean()

	ctx := context.Background()

	// Use UUID to ensure unique identifiers
	testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8] // 8 char UUID

	// First, create a test user to be the team owner
	testUser := &TestUserData{
		PreferredUsername: "teamowner" + testUUID,
		Email:             "teamowner" + testUUID + "@example.com",
		Password:          "TestPass123!",
		Name:              "Team Owner " + testUUID,
		GivenName:         "Team",
		FamilyName:        "Owner",
		Status:            "active",
		RoleID:            "admin",
		TypeID:            "regular",
		EmailVerified:     true,
		Metadata:          map[string]interface{}{"source": "test"},
	}

	userMap := maps.MapStrAny{
		"preferred_username": testUser.PreferredUsername,
		"email":              testUser.Email,
		"password":           testUser.Password,
		"name":               testUser.Name,
		"given_name":         testUser.GivenName,
		"family_name":        testUser.FamilyName,
		"status":             testUser.Status,
		"role_id":            testUser.RoleID,
		"type_id":            testUser.TypeID,
		"email_verified":     testUser.EmailVerified,
		"metadata":           testUser.Metadata,
	}

	// Create the owner user
	_, err := testProvider.CreateUser(ctx, userMap)
	assert.NoError(t, err)
	ownerUserID := userMap["user_id"].(string)

	// Create test team data dynamically
	testTeam := &TestTeamData{
		Name:        "Test Team " + testUUID,
		DisplayName: "Test Display " + testUUID,
		Description: "A test team for unit testing",
		Website:     "https://test" + testUUID + ".example.com",
		OwnerID:     ownerUserID,
		Status:      "active",
		Type:        "corporation",
		TypeID:      "free",
		Metadata:    map[string]interface{}{"test": true, "uuid": testUUID},
	}

	var testTeamID string // Store the auto-generated team_id

	// Test CreateTeam
	t.Run("CreateTeam", func(t *testing.T) {
		teamMap := maps.MapStrAny{
			"name":         testTeam.Name,
			"display_name": testTeam.DisplayName,
			"description":  testTeam.Description,
			"website":      testTeam.Website,
			"owner_id":     testTeam.OwnerID,
			"status":       testTeam.Status,
			"type":         testTeam.Type,
			"type_id":      testTeam.TypeID,
			"metadata":     testTeam.Metadata,
		}

		id, err := testProvider.CreateTeam(ctx, teamMap)
		assert.NoError(t, err)
		assert.NotEmpty(t, id)

		// Verify team was created with auto-generated team_id
		assert.Contains(t, teamMap, "team_id")
		assert.NotEmpty(t, teamMap["team_id"])

		// Store generated team_id for subsequent tests
		testTeamID = teamMap["team_id"].(string)
	})

	// Test GetTeam
	t.Run("GetTeam", func(t *testing.T) {
		team, err := testProvider.GetTeam(ctx, testTeamID)
		assert.NoError(t, err)
		assert.NotNil(t, team)
		assert.Equal(t, testTeam.Name, team["name"])
		assert.Equal(t, testTeam.DisplayName, team["display_name"])
		assert.Equal(t, testTeam.OwnerID, team["owner_id"])
		assert.Equal(t, testTeam.TypeID, team["type_id"])
	})

	// Test GetTeamDetail
	t.Run("GetTeamDetail", func(t *testing.T) {
		team, err := testProvider.GetTeamDetail(ctx, testTeamID)
		assert.NoError(t, err)
		assert.NotNil(t, team)
		assert.Equal(t, testTeam.Name, team["name"])
		assert.Equal(t, testTeam.Website, team["website"])
		assert.Equal(t, testTeam.Description, team["description"])
	})

	// Test TeamExists
	t.Run("TeamExists", func(t *testing.T) {
		exists, err := testProvider.TeamExists(ctx, testTeamID)
		assert.NoError(t, err)
		assert.True(t, exists)

		// Test with non-existent team
		exists, err = testProvider.TeamExists(ctx, "non-existent-team-"+testUUID)
		assert.NoError(t, err)
		assert.False(t, exists)
	})

	// Test UpdateTeam
	t.Run("UpdateTeam", func(t *testing.T) {
		updateData := maps.MapStrAny{
			"description":  "Updated test team description",
			"display_name": "Updated Display Name",
			"metadata":     map[string]interface{}{"updated": true},
		}

		err := testProvider.UpdateTeam(ctx, testTeamID, updateData)
		assert.NoError(t, err)

		// Verify update
		team, err := testProvider.GetTeam(ctx, testTeamID)
		assert.NoError(t, err)
		assert.Equal(t, "Updated test team description", team["description"])
		assert.Equal(t, "Updated Display Name", team["display_name"])

		// Test updating sensitive fields (should be ignored)
		sensitiveData := maps.MapStrAny{
			"team_id":     "new-team-id",
			"created_at":  "2023-01-01",
			"verified_at": "2023-01-01",
		}

		err = testProvider.UpdateTeam(ctx, testTeamID, sensitiveData)
		assert.NoError(t, err) // Should not error, just ignore sensitive fields
	})

	// Test UpdateTeamStatus
	t.Run("UpdateTeamStatus", func(t *testing.T) {
		err := testProvider.UpdateTeamStatus(ctx, testTeamID, "inactive")
		assert.NoError(t, err)

		// Verify status was updated
		team, err := testProvider.GetTeam(ctx, testTeamID)
		assert.NoError(t, err)
		assert.Equal(t, "inactive", team["status"])

		// Change back to active for other tests
		err = testProvider.UpdateTeamStatus(ctx, testTeamID, "active")
		assert.NoError(t, err)
	})

	// Test VerifyTeam
	t.Run("VerifyTeam", func(t *testing.T) {
		err := testProvider.VerifyTeam(ctx, testTeamID, ownerUserID)
		assert.NoError(t, err)

		// Verify team was marked as verified
		team, err := testProvider.GetTeamDetail(ctx, testTeamID)
		assert.NoError(t, err)
		// Database may return int64(1) instead of bool(true)
		isVerified := team["is_verified"]
		assert.True(t, isVerified == true || isVerified == int64(1) || isVerified == 1)
		// verified_by might be nil due to sensitive field filtering, just check it's not empty if present
		if verifiedBy := team["verified_by"]; verifiedBy != nil {
			assert.Equal(t, ownerUserID, verifiedBy)
		}
	})

	// Test UnverifyTeam
	t.Run("UnverifyTeam", func(t *testing.T) {
		err := testProvider.UnverifyTeam(ctx, testTeamID)
		assert.NoError(t, err)

		// Verify team was marked as unverified
		team, err := testProvider.GetTeamDetail(ctx, testTeamID)
		assert.NoError(t, err)
		// Database may return int64(0) instead of bool(false)
		isVerified := team["is_verified"]
		assert.True(t, isVerified == false || isVerified == int64(0) || isVerified == 0)
		assert.Nil(t, team["verified_by"])
	})

	// Test GetTeamsByOwner
	t.Run("GetTeamsByOwner", func(t *testing.T) {
		teams, err := testProvider.GetTeamsByOwner(ctx, ownerUserID)
		assert.NoError(t, err)
		assert.Len(t, teams, 1)
		assert.Equal(t, testTeamID, teams[0]["team_id"])
	})

	// Test GetTeamsByStatus
	t.Run("GetTeamsByStatus", func(t *testing.T) {
		teams, err := testProvider.GetTeamsByStatus(ctx, "active")
		assert.NoError(t, err)
		assert.True(t, len(teams) >= 1) // At least our test team

		// Find our test team in the results
		found := false
		for _, team := range teams {
			if team["team_id"] == testTeamID {
				found = true
				break
			}
		}
		assert.True(t, found, "Test team should be found in active teams")
	})

	// Test PaginateTeams
	t.Run("PaginateTeams", func(t *testing.T) {
		param := model.QueryParam{
			Wheres: []model.QueryWhere{
				{Column: "status", Value: "active"},
			},
		}

		result, err := testProvider.PaginateTeams(ctx, param, 1, 10)
		assert.NoError(t, err)
		assert.NotNil(t, result)
		// Pagination result may use "data" instead of "items"
		assert.True(t, result["data"] != nil || result["items"] != nil)
		assert.Contains(t, result, "total")
	})

	// Test CountTeams
	t.Run("CountTeams", func(t *testing.T) {
		param := model.QueryParam{
			Wheres: []model.QueryWhere{
				{Column: "status", Value: "active"},
			},
		}

		count, err := testProvider.CountTeams(ctx, param)
		assert.NoError(t, err)
		assert.True(t, count >= 1) // At least our test team
	})

	// Test DeleteTeam (at the end)
	t.Run("DeleteTeam", func(t *testing.T) {
		err := testProvider.DeleteTeam(ctx, testTeamID)
		assert.NoError(t, err)

		// Verify team was deleted
		_, err = testProvider.GetTeam(ctx, testTeamID)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "team not found")
	})
}

func TestTeamMemberOperations(t *testing.T) {
	prepare(t)
	defer clean()

	ctx := context.Background()

	// Use UUID to ensure unique identifiers
	testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]

	// Create test users
	ownerUser := createTestUser(ctx, t, "owner"+testUUID)
	memberUser := createTestUser(ctx, t, "member"+testUUID)

	// Create test team
	testTeam := &TestTeamData{
		Name:        "Member Test Team " + testUUID,
		DisplayName: "Member Test " + testUUID,
		Description: "A test team for member testing",
		OwnerID:     ownerUser,
		Status:      "active",
		Type:        "corporation",
		TypeID:      "business",
		Metadata:    map[string]interface{}{"test": true},
	}

	teamMap := maps.MapStrAny{
		"name":         testTeam.Name,
		"display_name": testTeam.DisplayName,
		"description":  testTeam.Description,
		"owner_id":     testTeam.OwnerID,
		"status":       testTeam.Status,
		"type":         testTeam.Type,
		"type_id":      testTeam.TypeID,
		"metadata":     testTeam.Metadata,
	}

	teamID, err := testProvider.CreateTeam(ctx, teamMap)
	assert.NoError(t, err)

	var businessMemberID string

	// Test AddMember (invitation-based)
	t.Run("AddMember", func(t *testing.T) {
		memberID, err := testProvider.AddMember(ctx, teamID, memberUser, "user", ownerUser)
		assert.NoError(t, err)
		assert.NotEmpty(t, memberID)
		businessMemberID = memberID
	})

	// Test MemberExists
	t.Run("MemberExists", func(t *testing.T) {
		exists, err := testProvider.MemberExists(ctx, teamID, memberUser)
		assert.NoError(t, err)
		assert.True(t, exists)

		// Test with non-existent member
		exists, err = testProvider.MemberExists(ctx, teamID, "non-existent-user")
		assert.NoError(t, err)
		assert.False(t, exists)
	})

	// Test GetMember
	t.Run("GetMember", func(t *testing.T) {
		member, err := testProvider.GetMember(ctx, teamID, memberUser)
		assert.NoError(t, err)
		assert.NotNil(t, member)
		assert.Equal(t, teamID, member["team_id"])
		assert.Equal(t, memberUser, member["user_id"])
		assert.Equal(t, "pending", member["status"]) // Initially pending
	})

	// Test GetMemberByMemberID
	t.Run("GetMemberByMemberID", func(t *testing.T) {
		member, err := testProvider.GetMemberByMemberID(ctx, businessMemberID)
		assert.NoError(t, err)
		assert.NotNil(t, member)
		assert.Equal(t, teamID, member["team_id"])
		assert.Equal(t, memberUser, member["user_id"])
	})

	// Test AcceptInvitation
	t.Run("AcceptInvitation", func(t *testing.T) {
		// First get the invitation token and invitation_id
		member, err := testProvider.GetMemberDetail(ctx, teamID, memberUser)
		assert.NoError(t, err)
		invitationToken := member["invitation_token"].(string)
		assert.NotEmpty(t, invitationToken)
		invitationID := member["invitation_id"].(string)
		assert.NotEmpty(t, invitationID)

		// Accept the invitation
		err = testProvider.AcceptInvitation(ctx, invitationID, invitationToken, "")
		assert.NoError(t, err)

		// Verify member status changed to active
		member, err = testProvider.GetMember(ctx, teamID, memberUser)
		assert.NoError(t, err)
		assert.Equal(t, "active", member["status"])
	})

	// Test UpdateMemberRole
	t.Run("UpdateMemberRole", func(t *testing.T) {
		err := testProvider.UpdateMemberRole(ctx, teamID, memberUser, "admin")
		assert.NoError(t, err)

		// Verify role was updated
		member, err := testProvider.GetMember(ctx, teamID, memberUser)
		assert.NoError(t, err)
		assert.Equal(t, "admin", member["role_id"])
	})

	// Test UpdateMemberStatus
	t.Run("UpdateMemberStatus", func(t *testing.T) {
		err := testProvider.UpdateMemberStatus(ctx, teamID, memberUser, "inactive")
		assert.NoError(t, err)

		// Verify status was updated
		member, err := testProvider.GetMember(ctx, teamID, memberUser)
		assert.NoError(t, err)
		assert.Equal(t, "inactive", member["status"])

		// Change back to active
		err = testProvider.UpdateMemberStatus(ctx, teamID, memberUser, "active")
		assert.NoError(t, err)
	})

	// Test UpdateMemberLastActivity
	t.Run("UpdateMemberLastActivity", func(t *testing.T) {
		err := testProvider.UpdateMemberLastActivity(ctx, teamID, memberUser)
		assert.NoError(t, err)

		// Verify last_active_at was updated
		member, err := testProvider.GetMember(ctx, teamID, memberUser)
		assert.NoError(t, err)
		assert.NotNil(t, member["last_active_at"])
	})

	// Test GetTeamMembers
	t.Run("GetTeamMembers", func(t *testing.T) {
		members, err := testProvider.GetTeamMembers(ctx, teamID)
		assert.NoError(t, err)
		assert.Len(t, members, 1) // Only our test member
		assert.Equal(t, memberUser, members[0]["user_id"])
	})

	// Test GetUserTeams
	t.Run("GetUserTeams", func(t *testing.T) {
		teams, err := testProvider.GetUserTeams(ctx, memberUser)
		assert.NoError(t, err)
		assert.Len(t, teams, 1) // Only our test team
		assert.Equal(t, teamID, teams[0]["team_id"])
	})

	// Test GetTeamMembersByStatus
	t.Run("GetTeamMembersByStatus", func(t *testing.T) {
		members, err := testProvider.GetTeamMembersByStatus(ctx, teamID, "active")
		assert.NoError(t, err)
		assert.Len(t, members, 1) // Our active member
		assert.Equal(t, memberUser, members[0]["user_id"])
	})

	// Test CreateRobotMember
	t.Run("CreateRobotMember", func(t *testing.T) {
		robotData := maps.MapStrAny{
			"display_name":    "TestBot" + testUUID,
			"bio":             "A test robot for unit testing",
			"role_id":         "bot",
			"autonomous_mode": true,
			"robot_status":    "idle",
		}

		robotMemberID, err := testProvider.CreateRobotMember(ctx, teamID, robotData)
		assert.NoError(t, err)
		assert.NotEmpty(t, robotMemberID)
	})

	// Test GetTeamRobotMembers
	t.Run("GetTeamRobotMembers", func(t *testing.T) {
		robots, err := testProvider.GetTeamRobotMembers(ctx, teamID)
		assert.NoError(t, err)
		assert.Len(t, robots, 1) // Our test robot
		assert.Equal(t, "robot", robots[0]["member_type"])
		assert.Equal(t, "TestBot"+testUUID, robots[0]["display_name"])
	})

	// Test RemoveMember (at the end)
	t.Run("RemoveMember", func(t *testing.T) {
		err := testProvider.RemoveMember(ctx, teamID, memberUser)
		assert.NoError(t, err)

		// Verify member was removed
		_, err = testProvider.GetMember(ctx, teamID, memberUser)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "member not found")
	})
}

func TestTeamErrorHandling(t *testing.T) {
	prepare(t)
	defer clean()

	ctx := context.Background()
	testUUID := strings.ReplaceAll(uuid.New().String(), "-", "")[:8]
	nonExistentTeamID := "non-existent-team-" + testUUID
	nonExistentUserID := "non-existent-user-" + testUUID

	t.Run("GetTeam_NotFound", func(t *testing.T) {
		_, err := testProvider.GetTeam(ctx, nonExistentTeamID)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "team not found")
	})

	t.Run("GetTeamDetail_NotFound", func(t *testing.T) {
		_, err := testProvider.GetTeamDetail(ctx, nonExistentTeamID)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "team not found")
	})

	t.Run("UpdateTeam_NotFound", func(t *testing.T) {
		updateData := maps.MapStrAny{"name": "Test"}
		err := testProvider.UpdateTeam(ctx, nonExistentTeamID, updateData)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "team not found")
	})

	t.Run("DeleteTeam_NotFound", func(t *testing.T) {
		err := testProvider.DeleteTeam(ctx, nonExistentTeamID)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "team not found")
	})

	t.Run("GetMember_NotFound", func(t *testing.T) {
		_, err := testProvider.GetMember(ctx, nonExistentTeamID, nonExistentUserID)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "member not found")
	})

	t.Run("RemoveMember_NotFound", func(t *testing.T) {
		err := testProvider.RemoveMember(ctx, nonExistentTeamID, nonExistentUserID)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "member not found")
	})

	t.Run("CreateTeam_MissingRequiredFields", func(t *testing.T) {
		// Missing name
		teamData := maps.MapStrAny{
			"owner_id": "test-owner",
		}
		_, err := testProvider.CreateTeam(ctx, teamData)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "name is required")

		// Missing owner_id
		teamData = maps.MapStrAny{
			"name": "Test Team",
		}
		_, err = testProvider.CreateTeam(ctx, teamData)
		assert.Error(t, err)
		assert.Contains(t, err.Error(), "owner_id is required")
	})
}

// Helper function to create a test user and return the user_id
func createTestUser(ctx context.Context, t *testing.T, suffix string) string {
	userMap := maps.MapStrAny{
		"preferred_username": "testuser" + suffix,
		"email":              "testuser" + suffix + "@example.com",
		"password":           "TestPass123!",
		"name":               "Test User " + suffix,
		"given_name":         "Test",
		"family_name":        "User",
		"status":             "active",
		"role_id":            "user",
		"type_id":            "regular",
		"email_verified":     true,
		"metadata":           map[string]interface{}{"source": "test"},
	}

	_, err := testProvider.CreateUser(ctx, userMap)
	assert.NoError(t, err)
	return userMap["user_id"].(string)
}
