package handler

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

	"github.com/gofiber/fiber/v2"
)

func newVerifyLoginCodeTestApp(h *AuthHandler) *fiber.App {
	app := fiber.New()
	app.Post("/api/v1/auth/login/code/verify", h.VerifyLoginCode)
	app.Post("/api/v1/auth/login/code/verify-short", h.VerifyLoginShortCode)
	return app
}

func decodeJSONBody(t *testing.T, resp *http.Response) map[string]any {
	t.Helper()

	var got map[string]any
	if err := json.NewDecoder(resp.Body).Decode(&got); err != nil {
		t.Fatalf("failed to decode response body: %v", err)
	}
	return got
}

func TestVerifyLoginCode_InvalidBody_ReturnsExplicitCode(t *testing.T) {
	h := &AuthHandler{}
	app := newVerifyLoginCodeTestApp(h)

	req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login/code/verify", bytes.NewBufferString("{"))
	req.Header.Set("Content-Type", "application/json")

	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d", resp.StatusCode)
	}

	got := decodeJSONBody(t, resp)
	if got["code"] != "bad_request" {
		t.Fatalf("expected code=bad_request, got %v", got["code"])
	}
}

func TestVerifyLoginCode_IdpUnavailable_ReturnsExplicitCode(t *testing.T) {
	h := &AuthHandler{}
	app := newVerifyLoginCodeTestApp(h)

	body, _ := json.Marshal(map[string]any{
		"loginId": "user@example.com",
		"code":    "AA-111111",
	})
	req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login/code/verify", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")

	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusServiceUnavailable {
		t.Fatalf("expected 503, got %d", resp.StatusCode)
	}

	got := decodeJSONBody(t, resp)
	if got["code"] != "service_unavailable" {
		t.Fatalf("expected code=service_unavailable, got %v", got["code"])
	}
}

func TestVerifyLoginCode_VerifyOnlyInvalidCode_ReturnsExplicitCode(t *testing.T) {
	redis := &mockRedisRepo{data: make(map[string]string)}
	redis.data[prefixLoginCode+"user@example.com"] = "flow-1"
	redis.data[prefixLoginCodePending+"user@example.com"] = "pending-1"
	redis.data[prefixLoginCodeValue+"pending-1"] = "AB-123"

	h := &AuthHandler{
		RedisService: redis,
		IdpProvider:  &mockIdpProvider{},
	}
	app := newVerifyLoginCodeTestApp(h)

	body, _ := json.Marshal(map[string]any{
		"loginId":    "user@example.com",
		"code":       "ZZ-999",
		"verifyOnly": true,
	})
	req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login/code/verify", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")

	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusUnauthorized {
		t.Fatalf("expected 401, got %d", resp.StatusCode)
	}

	got := decodeJSONBody(t, resp)
	if got["code"] != "invalid_code" {
		t.Fatalf("expected code=invalid_code, got %v", got["code"])
	}
}

func TestVerifyLoginShortCode_MissingShortCode_ReturnsExplicitCode(t *testing.T) {
	h := &AuthHandler{
		RedisService: &mockRedisRepo{data: make(map[string]string)},
	}
	app := newVerifyLoginCodeTestApp(h)

	body, _ := json.Marshal(map[string]any{
		"shortCode": "",
	})
	req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login/code/verify-short", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")

	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d", resp.StatusCode)
	}

	got := decodeJSONBody(t, resp)
	if got["code"] != "bad_request" {
		t.Fatalf("expected code=bad_request, got %v", got["code"])
	}
}

func TestVerifyLoginShortCode_InvalidOrExpired_ReturnsExplicitCode(t *testing.T) {
	h := &AuthHandler{
		RedisService: &mockRedisRepo{data: make(map[string]string)},
	}
	app := newVerifyLoginCodeTestApp(h)

	body, _ := json.Marshal(map[string]any{
		"shortCode": "AB-123456",
	})
	req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login/code/verify-short", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")

	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusUnauthorized {
		t.Fatalf("expected 401, got %d", resp.StatusCode)
	}

	got := decodeJSONBody(t, resp)
	if got["code"] != "invalid_or_expired_code" {
		t.Fatalf("expected code=invalid_or_expired_code, got %v", got["code"])
	}
}

func TestVerifyLoginShortCode_VerifyOnlyMissingPendingRef_ReturnsExplicitCode(t *testing.T) {
	redis := &mockRedisRepo{data: make(map[string]string)}
	payload, _ := json.Marshal(shortLoginCodePayload{
		LoginID: "user@example.com",
		Code:    "AB-123",
	})
	redis.data[prefixLoginCodeShort+"AB-123456"] = string(payload)

	h := &AuthHandler{
		RedisService: redis,
	}
	app := newVerifyLoginCodeTestApp(h)

	body, _ := json.Marshal(map[string]any{
		"shortCode":  "AB-123456",
		"verifyOnly": true,
	})
	req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/login/code/verify-short", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")

	resp, err := app.Test(req)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d", resp.StatusCode)
	}

	got := decodeJSONBody(t, resp)
	if got["code"] != "invalid_session_reference" {
		t.Fatalf("expected code=invalid_session_reference, got %v", got["code"])
	}
}
