package handler

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/stretchr/testify/assert"
)

// --- Mock Redis ---

type mockRedisRepo struct {
	data map[string]string
}

func (m *mockRedisRepo) Set(key, value string, ttl time.Duration) error {
	if m.data == nil {
		m.data = make(map[string]string)
	}
	m.data[key] = value
	return nil
}

func (m *mockRedisRepo) Get(key string) (string, error) {
	// Bypass rate limiting for tests
	if strings.HasPrefix(key, "poll_meta:") {
		return "", nil
	}
	return m.data[key], nil
}

func (m *mockRedisRepo) Delete(key string) error {
	delete(m.data, key)
	return nil
}

func (m *mockRedisRepo) StoreVerificationCode(phone, code string) error {
	return m.Set("sms:"+phone, code, time.Minute)
}

func (m *mockRedisRepo) GetVerificationCode(phone string) (string, error) {
	return m.Get("sms:" + phone)
}

func (m *mockRedisRepo) DeleteVerificationCode(phone string) error {
	return m.Delete("sms:" + phone)
}

// --- Tests ---

func TestQRLoginFlow_Success(t *testing.T) {
	redis := &mockRedisRepo{data: make(map[string]string)}
	h := &AuthHandler{
		RedisService: redis,
	}
	app := fiber.New()
	app.Post("/api/v1/auth/qr/init", h.InitQRLogin)
	app.Post("/api/v1/auth/qr/poll", h.PollQRLogin)

	// 1. Init QR Login
	req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/qr/init", nil)
	resp, _ := app.Test(req, -1)
	assert.Equal(t, http.StatusOK, resp.StatusCode)

	var initResp map[string]any
	json.NewDecoder(resp.Body).Decode(&initResp)
	pendingRef := initResp["pendingRef"].(string)

	// 2. Poll (Pending)
	body, _ := json.Marshal(map[string]string{"pendingRef": pendingRef})
	req = httptest.NewRequest(http.MethodPost, "/api/v1/auth/qr/poll", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, _ = app.Test(req, -1)

	// Expect authorization_pending (400)
	assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
	var pollResp map[string]any
	json.NewDecoder(resp.Body).Decode(&pollResp)
	assert.Equal(t, "authorization_pending", pollResp["error"])
	assert.Equal(t, "authorization_pending", pollResp["code"])

	// 3. Mock Approval
	sessionData, _ := json.Marshal(map[string]string{
		"status": "success",
		"jwt":    "mock-session-jwt",
	})
	redis.data["enchanted_session:"+pendingRef] = string(sessionData)

	// 4. Poll (Success)
	req = httptest.NewRequest(http.MethodPost, "/api/v1/auth/qr/poll", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, _ = app.Test(req, -1)

	assert.Equal(t, http.StatusOK, resp.StatusCode)

	var successResp map[string]any
	json.NewDecoder(resp.Body).Decode(&successResp)
	assert.Equal(t, "ok", successResp["status"])
	assert.Equal(t, "mock-session-jwt", successResp["sessionJwt"])
}

func TestScanQRLogin_Success(t *testing.T) {
	redis := &mockRedisRepo{data: make(map[string]string)}
	idp := &mockIdpProvider{userExists: true}

	h := &AuthHandler{
		RedisService: redis,
		IdpProvider:  idp,
	}
	app := fiber.New()
	app.Post("/api/v1/auth/qr/approve", h.ScanQRLogin)

	pendingRef := "test-ref"
	redis.data["enchanted_session:"+pendingRef] = `{"status":"pending"}`

	transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
		if r.URL.Path == "/sessions/whoami" {
			return httpJSONAny(r, http.StatusOK, map[string]any{
				"identity": map[string]any{
					"id": "user-123",
					"traits": map[string]any{
						"email": "user@example.com",
					},
				},
			}), nil
		}
		return httpResponse(r, http.StatusNotFound, "not found"), nil
	})

	origDefault := http.DefaultClient
	http.DefaultClient = &http.Client{Transport: transport}
	defer func() { http.DefaultClient = origDefault }()

	body, _ := json.Marshal(map[string]string{
		"pendingRef": pendingRef,
		"token":      "valid-token",
	})
	req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/qr/approve", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	resp, _ := app.Test(req, -1)

	assert.Equal(t, http.StatusOK, resp.StatusCode)
}

func TestResolveConsentSubjects_TokenAndCookie(t *testing.T) {
	h := &AuthHandler{}

	transport := roundTripFunc(func(r *http.Request) (*http.Response, error) {
		if r.Header.Get("X-Session-Token") == "token-123" {
			return httpJSONAny(r, http.StatusOK, map[string]any{
				"identity": map[string]any{
					"id": "user-token",
					"traits": map[string]any{
						"email": "token@test.com",
					},
				},
			}), nil
		}
		if r.Header.Get("Cookie") == "ory_kratos_session=cookie-123" {
			return httpJSONAny(r, http.StatusOK, map[string]any{
				"identity": map[string]any{
					"id": "user-cookie",
					"traits": map[string]any{
						"email": "cookie@test.com",
						"phone": "010-1234-5678",
					},
				},
			}), nil
		}
		return httpResponse(r, http.StatusUnauthorized, "unauthorized"), nil
	})

	origDefault := http.DefaultClient
	http.DefaultClient = &http.Client{Transport: transport}
	defer func() { http.DefaultClient = origDefault }()

	app := fiber.New()

	// Token case
	app.Get("/test-token", func(c *fiber.Ctx) error {
		subjects, err := h.resolveConsentSubjects(c)
		assert.NoError(t, err)
		assert.Contains(t, subjects, "user-token")
		return c.SendStatus(200)
	})
	req := httptest.NewRequest("GET", "/test-token", nil)
	req.Header.Set("Authorization", "Bearer token-123")
	app.Test(req, -1)

	// Cookie case
	app.Get("/test-cookie", func(c *fiber.Ctx) error {
		subjects, err := h.resolveConsentSubjects(c)
		assert.NoError(t, err)
		assert.Contains(t, subjects, "user-cookie")
		return c.SendStatus(200)
	})
	req = httptest.NewRequest("GET", "/test-cookie", nil)
	req.Header.Set("Cookie", "ory_kratos_session=cookie-123")
	app.Test(req, -1)
}
