package security_test

import (
	"context"
	"errors"
	"net/http"
	"testing"
	"time"

	"github.com/bitechdev/ResolveSpec/pkg/security"
)

var ErrInvalidCredentials = errors.New("invalid credentials")

// MockAuthenticator is a simple authenticator for testing 2FA
type MockAuthenticator struct {
	users map[string]*security.UserContext
}

func NewMockAuthenticator() *MockAuthenticator {
	return &MockAuthenticator{
		users: map[string]*security.UserContext{
			"testuser": {
				UserID:   1,
				UserName: "testuser",
				Email:    "test@example.com",
			},
		},
	}
}

func (m *MockAuthenticator) Login(ctx context.Context, req security.LoginRequest) (*security.LoginResponse, error) {
	user, exists := m.users[req.Username]
	if !exists || req.Password != "password" {
		return nil, ErrInvalidCredentials
	}

	return &security.LoginResponse{
		Token:        "mock-token",
		RefreshToken: "mock-refresh-token",
		User:         user,
		ExpiresIn:    3600,
	}, nil
}

func (m *MockAuthenticator) LoginWithCookie(ctx context.Context, req security.LoginRequest, _ http.ResponseWriter) (*security.LoginResponse, error) {
	return m.Login(ctx, req)
}

func (m *MockAuthenticator) Logout(ctx context.Context, req security.LogoutRequest) error {
	return nil
}

func (m *MockAuthenticator) LogoutWithCookie(ctx context.Context, req security.LogoutRequest, _ http.ResponseWriter) error {
	return m.Logout(ctx, req)
}

func (m *MockAuthenticator) Authenticate(r *http.Request) (*security.UserContext, error) {
	return m.users["testuser"], nil
}

func (m *MockAuthenticator) SetAuthenticateCallback(_ func(r *http.Request) (*security.UserContext, error)) {}

func TestTwoFactorAuthenticator_Setup(t *testing.T) {
	baseAuth := NewMockAuthenticator()
	provider := security.NewMemoryTwoFactorProvider(nil)
	tfaAuth := security.NewTwoFactorAuthenticator(baseAuth, provider, nil)

	// Setup 2FA
	secret, err := tfaAuth.Setup2FA(1, "TestApp", "test@example.com")
	if err != nil {
		t.Fatalf("Setup2FA() error = %v", err)
	}

	if secret.Secret == "" {
		t.Error("Setup2FA() returned empty secret")
	}

	if secret.QRCodeURL == "" {
		t.Error("Setup2FA() returned empty QR code URL")
	}

	if len(secret.BackupCodes) == 0 {
		t.Error("Setup2FA() returned no backup codes")
	}

	if secret.Issuer != "TestApp" {
		t.Errorf("Setup2FA() Issuer = %s, want TestApp", secret.Issuer)
	}

	if secret.AccountName != "test@example.com" {
		t.Errorf("Setup2FA() AccountName = %s, want test@example.com", secret.AccountName)
	}
}

func TestTwoFactorAuthenticator_Enable2FA(t *testing.T) {
	baseAuth := NewMockAuthenticator()
	provider := security.NewMemoryTwoFactorProvider(nil)
	tfaAuth := security.NewTwoFactorAuthenticator(baseAuth, provider, nil)

	// Setup 2FA
	secret, err := tfaAuth.Setup2FA(1, "TestApp", "test@example.com")
	if err != nil {
		t.Fatalf("Setup2FA() error = %v", err)
	}

	// Generate valid code
	totp := security.NewTOTPGenerator(nil)
	code, err := totp.GenerateCode(secret.Secret, time.Now())
	if err != nil {
		t.Fatalf("GenerateCode() error = %v", err)
	}

	// Enable 2FA with valid code
	err = tfaAuth.Enable2FA(1, secret.Secret, code)
	if err != nil {
		t.Errorf("Enable2FA() error = %v", err)
	}

	// Verify 2FA is enabled
	status, err := provider.Get2FAStatus(1)
	if err != nil {
		t.Fatalf("Get2FAStatus() error = %v", err)
	}

	if !status {
		t.Error("Enable2FA() did not enable 2FA")
	}
}

func TestTwoFactorAuthenticator_Enable2FA_InvalidCode(t *testing.T) {
	baseAuth := NewMockAuthenticator()
	provider := security.NewMemoryTwoFactorProvider(nil)
	tfaAuth := security.NewTwoFactorAuthenticator(baseAuth, provider, nil)

	// Setup 2FA
	secret, err := tfaAuth.Setup2FA(1, "TestApp", "test@example.com")
	if err != nil {
		t.Fatalf("Setup2FA() error = %v", err)
	}

	// Try to enable with invalid code
	err = tfaAuth.Enable2FA(1, secret.Secret, "000000")
	if err == nil {
		t.Error("Enable2FA() should fail with invalid code")
	}

	// Verify 2FA is not enabled
	status, _ := provider.Get2FAStatus(1)
	if status {
		t.Error("Enable2FA() should not enable 2FA with invalid code")
	}
}

func TestTwoFactorAuthenticator_Login_Without2FA(t *testing.T) {
	baseAuth := NewMockAuthenticator()
	provider := security.NewMemoryTwoFactorProvider(nil)
	tfaAuth := security.NewTwoFactorAuthenticator(baseAuth, provider, nil)

	req := security.LoginRequest{
		Username: "testuser",
		Password: "password",
	}

	resp, err := tfaAuth.Login(context.Background(), req)
	if err != nil {
		t.Fatalf("Login() error = %v", err)
	}

	if resp.Requires2FA {
		t.Error("Login() should not require 2FA when not enabled")
	}

	if resp.Token == "" {
		t.Error("Login() should return token when 2FA not required")
	}
}

func TestTwoFactorAuthenticator_Login_With2FA_NoCode(t *testing.T) {
	baseAuth := NewMockAuthenticator()
	provider := security.NewMemoryTwoFactorProvider(nil)
	tfaAuth := security.NewTwoFactorAuthenticator(baseAuth, provider, nil)

	// Setup and enable 2FA
	secret, _ := tfaAuth.Setup2FA(1, "TestApp", "test@example.com")
	totp := security.NewTOTPGenerator(nil)
	code, _ := totp.GenerateCode(secret.Secret, time.Now())
	tfaAuth.Enable2FA(1, secret.Secret, code)

	// Try to login without 2FA code
	req := security.LoginRequest{
		Username: "testuser",
		Password: "password",
	}

	resp, err := tfaAuth.Login(context.Background(), req)
	if err != nil {
		t.Fatalf("Login() error = %v", err)
	}

	if !resp.Requires2FA {
		t.Error("Login() should require 2FA when enabled")
	}

	if resp.Token != "" {
		t.Error("Login() should not return token when 2FA required but not provided")
	}
}

func TestTwoFactorAuthenticator_Login_With2FA_ValidCode(t *testing.T) {
	baseAuth := NewMockAuthenticator()
	provider := security.NewMemoryTwoFactorProvider(nil)
	tfaAuth := security.NewTwoFactorAuthenticator(baseAuth, provider, nil)

	// Setup and enable 2FA
	secret, _ := tfaAuth.Setup2FA(1, "TestApp", "test@example.com")
	totp := security.NewTOTPGenerator(nil)
	code, _ := totp.GenerateCode(secret.Secret, time.Now())
	tfaAuth.Enable2FA(1, secret.Secret, code)

	// Generate new valid code for login
	newCode, _ := totp.GenerateCode(secret.Secret, time.Now())

	// Login with 2FA code
	req := security.LoginRequest{
		Username:      "testuser",
		Password:      "password",
		TwoFactorCode: newCode,
	}

	resp, err := tfaAuth.Login(context.Background(), req)
	if err != nil {
		t.Fatalf("Login() error = %v", err)
	}

	if resp.Requires2FA {
		t.Error("Login() should not require 2FA when valid code provided")
	}

	if resp.Token == "" {
		t.Error("Login() should return token when 2FA validated")
	}

	if !resp.User.TwoFactorEnabled {
		t.Error("Login() should set TwoFactorEnabled on user")
	}
}

func TestTwoFactorAuthenticator_Login_With2FA_InvalidCode(t *testing.T) {
	baseAuth := NewMockAuthenticator()
	provider := security.NewMemoryTwoFactorProvider(nil)
	tfaAuth := security.NewTwoFactorAuthenticator(baseAuth, provider, nil)

	// Setup and enable 2FA
	secret, _ := tfaAuth.Setup2FA(1, "TestApp", "test@example.com")
	totp := security.NewTOTPGenerator(nil)
	code, _ := totp.GenerateCode(secret.Secret, time.Now())
	tfaAuth.Enable2FA(1, secret.Secret, code)

	// Try to login with invalid code
	req := security.LoginRequest{
		Username:      "testuser",
		Password:      "password",
		TwoFactorCode: "000000",
	}

	_, err := tfaAuth.Login(context.Background(), req)
	if err == nil {
		t.Error("Login() should fail with invalid 2FA code")
	}
}

func TestTwoFactorAuthenticator_Login_WithBackupCode(t *testing.T) {
	baseAuth := NewMockAuthenticator()
	provider := security.NewMemoryTwoFactorProvider(nil)
	tfaAuth := security.NewTwoFactorAuthenticator(baseAuth, provider, nil)

	// Setup and enable 2FA
	secret, _ := tfaAuth.Setup2FA(1, "TestApp", "test@example.com")
	totp := security.NewTOTPGenerator(nil)
	code, _ := totp.GenerateCode(secret.Secret, time.Now())
	tfaAuth.Enable2FA(1, secret.Secret, code)

	// Get backup codes
	backupCodes, _ := tfaAuth.RegenerateBackupCodes(1, 10)

	// Login with backup code
	req := security.LoginRequest{
		Username:      "testuser",
		Password:      "password",
		TwoFactorCode: backupCodes[0],
	}

	resp, err := tfaAuth.Login(context.Background(), req)
	if err != nil {
		t.Fatalf("Login() with backup code error = %v", err)
	}

	if resp.Token == "" {
		t.Error("Login() should return token when backup code validated")
	}

	// Try to use same backup code again
	req2 := security.LoginRequest{
		Username:      "testuser",
		Password:      "password",
		TwoFactorCode: backupCodes[0],
	}

	_, err = tfaAuth.Login(context.Background(), req2)
	if err == nil {
		t.Error("Login() should fail when reusing backup code")
	}
}

func TestTwoFactorAuthenticator_Disable2FA(t *testing.T) {
	baseAuth := NewMockAuthenticator()
	provider := security.NewMemoryTwoFactorProvider(nil)
	tfaAuth := security.NewTwoFactorAuthenticator(baseAuth, provider, nil)

	// Setup and enable 2FA
	secret, _ := tfaAuth.Setup2FA(1, "TestApp", "test@example.com")
	totp := security.NewTOTPGenerator(nil)
	code, _ := totp.GenerateCode(secret.Secret, time.Now())
	tfaAuth.Enable2FA(1, secret.Secret, code)

	// Disable 2FA
	err := tfaAuth.Disable2FA(1)
	if err != nil {
		t.Errorf("Disable2FA() error = %v", err)
	}

	// Verify 2FA is disabled
	status, _ := provider.Get2FAStatus(1)
	if status {
		t.Error("Disable2FA() did not disable 2FA")
	}

	// Login should not require 2FA
	req := security.LoginRequest{
		Username: "testuser",
		Password: "password",
	}

	resp, err := tfaAuth.Login(context.Background(), req)
	if err != nil {
		t.Fatalf("Login() error = %v", err)
	}

	if resp.Requires2FA {
		t.Error("Login() should not require 2FA after disabling")
	}
}

func TestTwoFactorAuthenticator_RegenerateBackupCodes(t *testing.T) {
	baseAuth := NewMockAuthenticator()
	provider := security.NewMemoryTwoFactorProvider(nil)
	tfaAuth := security.NewTwoFactorAuthenticator(baseAuth, provider, nil)

	// Setup and enable 2FA
	secret, _ := tfaAuth.Setup2FA(1, "TestApp", "test@example.com")
	totp := security.NewTOTPGenerator(nil)
	code, _ := totp.GenerateCode(secret.Secret, time.Now())
	tfaAuth.Enable2FA(1, secret.Secret, code)

	// Get initial backup codes
	codes1, err := tfaAuth.RegenerateBackupCodes(1, 10)
	if err != nil {
		t.Fatalf("RegenerateBackupCodes() error = %v", err)
	}

	if len(codes1) != 10 {
		t.Errorf("RegenerateBackupCodes() returned %d codes, want 10", len(codes1))
	}

	// Regenerate backup codes
	codes2, err := tfaAuth.RegenerateBackupCodes(1, 10)
	if err != nil {
		t.Fatalf("RegenerateBackupCodes() error = %v", err)
	}

	// Old codes should not work
	req := security.LoginRequest{
		Username:      "testuser",
		Password:      "password",
		TwoFactorCode: codes1[0],
	}

	_, err = tfaAuth.Login(context.Background(), req)
	if err == nil {
		t.Error("Login() should fail with old backup code after regeneration")
	}

	// New codes should work
	req2 := security.LoginRequest{
		Username:      "testuser",
		Password:      "password",
		TwoFactorCode: codes2[0],
	}

	resp, err := tfaAuth.Login(context.Background(), req2)
	if err != nil {
		t.Fatalf("Login() with new backup code error = %v", err)
	}

	if resp.Token == "" {
		t.Error("Login() should return token with new backup code")
	}
}
