package handler

import (
	"context"
	"crypto/rand"
	"encoding/binary"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"strconv"
	"testing"
	"unicode"

	"github.com/descope/go-sdk/descope"
	"github.com/descope/go-sdk/descope/client"
	mocksauth "github.com/descope/go-sdk/descope/tests/mocks/auth"
)

// 정책을 받아 필수 요구사항을 모두 포함하는 비밀번호를 생성한다.
func generatePasswordFromPolicy(policy *descope.PasswordPolicy) string {
	minLen := int(policy.MinLength)
	if minLen < 8 {
		minLen = 12 // 안전한 기본값
	}

	pwd := make([]rune, 0, minLen)

	if policy.Lowercase {
		pwd = append(pwd, 'a')
	}
	if policy.Uppercase {
		pwd = append(pwd, 'B')
	}
	if policy.Number {
		pwd = append(pwd, '3')
	}
	if policy.NonAlphanumeric {
		pwd = append(pwd, '!')
	}

	charset := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
	for len(pwd) < minLen {
		pwd = append(pwd, rune(charset[randomInt(len(charset))]))
	}

	// 섞어서 예측 가능성을 낮춘다.
	for i := range pwd {
		j := randomInt(len(pwd))
		pwd[i], pwd[j] = pwd[j], pwd[i]
	}
	return string(pwd)
}

func randomInt(n int) int {
	if n <= 0 {
		return 0
	}
	var b [8]byte
	if _, err := rand.Read(b[:]); err != nil {
		return 0
	}
	return int(binary.BigEndian.Uint64(b[:]) % uint64(n))
}

func TestGeneratePasswordUsesNonAlphanumericRequirement(t *testing.T) {
	mockAuth := &mocksauth.MockAuthentication{
		MockPassword: &mocksauth.MockPassword{
			PolicyResponse: &descope.PasswordPolicy{
				MinLength:       8,
				Lowercase:       true,
				Uppercase:       true,
				Number:          true,
				NonAlphanumeric: true,
			},
		},
	}

	policy, err := mockAuth.Password().GetPasswordPolicy(context.Background())
	if err != nil {
		t.Fatalf("정책 조회 실패: %v", err)
	}
	if !policy.NonAlphanumeric {
		t.Fatalf("정책에 비영문자 요구사항이 표시되지 않음")
	}

	pwd := generatePasswordFromPolicy(policy)

	if len(pwd) < int(policy.MinLength) {
		t.Fatalf("비밀번호 길이가 정책 최소 길이 미만: got %d, want >= %d", len(pwd), policy.MinLength)
	}

	var hasLower, hasUpper, hasNumber, hasSymbol bool
	for _, r := range pwd {
		switch {
		case unicode.IsLower(r):
			hasLower = true
		case unicode.IsUpper(r):
			hasUpper = true
		case unicode.IsNumber(r):
			hasNumber = true
		case !unicode.IsLetter(r) && !unicode.IsNumber(r):
			hasSymbol = true
		}
	}

	if policy.Lowercase && !hasLower {
		t.Fatalf("소문자 요구사항 미충족: %q", pwd)
	}
	if policy.Uppercase && !hasUpper {
		t.Fatalf("대문자 요구사항 미충족: %q", pwd)
	}
	if policy.Number && !hasNumber {
		t.Fatalf("숫자 요구사항 미충족: %q", pwd)
	}
	if policy.NonAlphanumeric && !hasSymbol {
		t.Fatalf("비영문자 요구사항 미충족: %q", pwd)
	}
}

// 통합 테스트: 실제 Descope 정책으로 비밀번호를 생성하고 교체 플로우를 검증한다.
// 필요 env:
// DESCOPE_PROJECT_ID, DESCOPE_MANAGEMENT_KEY, TEST_DESCOPE_LOGIN_ID, TEST_DESCOPE_CURRENT_PASSWORD
func TestDescopePasswordPolicyAndChange(t *testing.T) {
	projectID := os.Getenv("DESCOPE_PROJECT_ID")
	managementKey := os.Getenv("DESCOPE_MANAGEMENT_KEY")
	loginID := os.Getenv("DESCOPE_TEST_ACCOUNT")

	if projectID == "" || managementKey == "" || loginID == "" {
		t.Skip("환경변수(DESCOPE_PROJECT_ID, DESCOPE_MANAGEMENT_KEY, DESCOPE_TEST_ACCOUNT) 미설정으로 통합 테스트 건너뜀")
	}

	logf := func(format string, args ...any) {
		t.Logf(format, args...)
		fmt.Printf(format+"\n", args...)
	}

	ctx := context.Background()
	cl, err := client.NewWithConfig(&client.Config{
		ProjectID:     projectID,
		ManagementKey: managementKey,
	})
	if err != nil {
		t.Fatalf("Descope 클라이언트 초기화 실패: %v", err)
	}

	policy, err := cl.Auth.Password().GetPasswordPolicy(ctx)
	if err != nil {
		t.Fatalf("비밀번호 정책 조회 실패: %v", err)
	}
	logf("정책: min=%d lower=%v upper=%v number=%v nonAlpha=%v", policy.MinLength, policy.Lowercase, policy.Uppercase, policy.Number, policy.NonAlphanumeric)

	// 테스트 계정이 없으면 생성
	users, _, err := cl.Management.User().SearchAll(ctx, &descope.UserSearchOptions{
		LoginIDs: []string{loginID},
		Limit:    1,
		Page:     0,
	})
	if err != nil {
		t.Fatalf("테스트 계정 검색 실패: %v", err)
	}
	if len(users) == 0 {
		logf("테스트 계정 미존재, 생성 시도: %s", loginID)
		if _, err := cl.Management.User().CreateTestUser(ctx, loginID, &descope.UserRequest{
			User: descope.User{
				Email: loginID,
			},
		}); err != nil {
			t.Fatalf("테스트 계정 생성 실패: %v", err)
		}
	} else {
		logf("테스트 계정 존재 확인: %s", loginID)
	}

	// 1) 기초 비밀번호 설정 (알려진 값으로 초기화)
	basePassword := generatePasswordFromPolicy(policy)
	if err := cl.Management.User().SetActivePassword(ctx, loginID, basePassword); err != nil {
		logf("초기 비밀번호 설정 실패: status=%d err=%v", statusFromError(err), err)
		t.Fatalf("초기 비밀번호 설정 실패: %v", err)
	}
	logf("초기 비밀번호 설정 완료: %s", basePassword)

	// 2) 초기 비밀번호 로그인 검증
	wOld := httptest.NewRecorder()
	_, err = cl.Auth.Password().SignIn(ctx, loginID, basePassword, wOld)
	logf("기초 비밀번호 로그인: status=%d err=%v", statusFromError(err), err)
	if err != nil {
		t.Fatalf("기초 비밀번호 로그인 실패: %v", err)
	}

	// 3) 새 비밀번호 생성 및 변경
	newPassword := generatePasswordFromPolicy(policy)
	if newPassword == basePassword {
		newPassword = newPassword + "Z9!"
	}
	logf("새 비밀번호 생성: %s", newPassword)

	if err := cl.Management.User().SetActivePassword(ctx, loginID, newPassword); err != nil {
		logf("비밀번호 변경 실패: status=%d err=%v", statusFromError(err), err)
		t.Fatalf("비밀번호 변경 실패: %v", err)
	}
	logf("비밀번호 변경 성공(status=200)")

	// 4) 새 비밀번호로 로그인 확인
	wNew := httptest.NewRecorder()
	_, err = cl.Auth.Password().SignIn(ctx, loginID, newPassword, wNew)
	logf("새 비밀번호 로그인: status=%d err=%v", statusFromError(err), err)
	if err != nil {
		t.Fatalf("새 비밀번호 로그인 실패: %v", err)
	}
}

func statusFromError(err error) int {
	if err == nil {
		return http.StatusOK
	}
	var de *descope.Error
	if errors.As(err, &de) {
		if statusRaw, ok := de.Info[descope.ErrorInfoKeys.HTTPResponseStatusCode]; ok {
			switch v := statusRaw.(type) {
			case int:
				return v
			case string:
				if n, convErr := strconv.Atoi(v); convErr == nil {
					return n
				}
			}
		}
	}
	return http.StatusInternalServerError
}
