package httpapi

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

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/clients"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/publicerror"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)

func TestPublicHTTPErrorReportsSourceAndPreservesSafeUpstreamStatus(t *testing.T) {
	upstream := httptest.NewRecorder()
	writeProtocolError(upstream, clients.ProtocolOpenAIImages, http.StatusNotFound, "404 page not found at private upstream route", nil, "http_404")
	if upstream.Code != http.StatusNotFound {
		t.Fatalf("upstream status = %d, want 404; body=%s", upstream.Code, upstream.Body.String())
	}
	var upstreamBody map[string]any
	if err := json.Unmarshal(upstream.Body.Bytes(), &upstreamBody); err != nil {
		t.Fatal(err)
	}
	upstreamError := requireObject(t, upstreamBody["error"])
	upstreamDetails := requireObject(t, upstreamError["details"])
	upstreamPublic := requireObject(t, upstreamDetails["publicError"])
	if upstreamError["code"] != "upstream_not_found" || upstreamPublic["source"] != "upstream" {
		t.Fatalf("unexpected upstream response: %+v", upstreamBody)
	}
	if strings.Contains(upstream.Body.String(), "private upstream route") {
		t.Fatalf("upstream response leaked raw message: %s", upstream.Body.String())
	}

	gateway := httptest.NewRecorder()
	writeError(gateway, http.StatusTooManyRequests, "concurrency limit is saturated and queueing is disabled", "gateway_rate_limited")
	if gateway.Code != http.StatusTooManyRequests {
		t.Fatalf("gateway status = %d, want 429; body=%s", gateway.Code, gateway.Body.String())
	}
	var gatewayBody map[string]any
	if err := json.Unmarshal(gateway.Body.Bytes(), &gatewayBody); err != nil {
		t.Fatal(err)
	}
	gatewayError := requireObject(t, gatewayBody["error"])
	if gatewayError["code"] != "gateway_rate_limited" || gatewayError["source"] != "gateway" {
		t.Fatalf("unexpected gateway response: %+v", gatewayBody)
	}
}

func TestPublicHTTPErrorIncludesSafeUpstreamParameterMessage(t *testing.T) {
	raw := "Duplicate parameter: 'image'. Use image[]=<value> for multiple values."
	recorder := httptest.NewRecorder()
	writeProtocolError(recorder, clients.ProtocolOpenAIImages, http.StatusBadRequest, raw, nil, "http_400")
	if recorder.Code != http.StatusBadRequest {
		t.Fatalf("status = %d, want 400; body=%s", recorder.Code, recorder.Body.String())
	}
	var body map[string]any
	if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	errorPayload := requireObject(t, body["error"])
	details := requireObject(t, errorPayload["details"])
	upstream := requireObject(t, details["upstreamError"])
	if errorPayload["message"] != raw || upstream["message"] != raw || upstream["code"] != "http_400" || upstream["statusCode"] != float64(http.StatusBadRequest) {
		t.Fatalf("unexpected upstream error details: %+v", body)
	}
}

func TestPublicGatewayTaskSanitizesCopyAndKeepsRawAuditFieldsInternal(t *testing.T) {
	rawMessage := `404 page not found: {"privateProject":"secret"}`
	task := store.GatewayTask{
		ID:           "task-1",
		Status:       "failed",
		ErrorCode:    "http_404",
		ErrorMessage: rawMessage,
		Attempts: []store.TaskAttempt{{
			AttemptNo:        1,
			Status:           "failed",
			StatusCode:       http.StatusNotFound,
			ErrorCode:        "http_404",
			ErrorMessage:     rawMessage,
			RequestSnapshot:  map[string]any{"image_base64": "private-input"},
			ResponseSnapshot: map[string]any{"provider_payload": "private-output"},
		}},
	}

	public := publicGatewayTask(task)
	if public.ErrorCode != "upstream_not_found" || public.ErrorMessage == rawMessage || public.PublicError == nil || public.PublicError.Source != "upstream" {
		t.Fatalf("unexpected public task error: %+v", public)
	}
	if strings.Contains(public.ErrorMessage, "secret") || public.Attempts[0].ErrorMessage == rawMessage {
		t.Fatalf("public task leaked upstream details: %+v", public)
	}
	if public.Attempts[0].RequestSnapshot != nil || public.Attempts[0].ResponseSnapshot != nil {
		t.Fatalf("public task leaked attempt snapshots: %+v", public.Attempts[0])
	}
	if task.ErrorCode != "http_404" || task.ErrorMessage != rawMessage || task.Attempts[0].ErrorCode != "http_404" || task.Attempts[0].ErrorMessage != rawMessage {
		t.Fatalf("public conversion mutated raw audit fields: %+v", task)
	}
	if task.Attempts[0].RequestSnapshot == nil || task.Attempts[0].ResponseSnapshot == nil {
		t.Fatalf("public conversion mutated internal snapshots: %+v", task.Attempts[0])
	}
}

func TestPublicGatewayTaskForwardsSafeUpstreamParameterMessage(t *testing.T) {
	raw := "Duplicate parameter: 'image'. Use image[]=<value> for multiple values."
	legacy := publicerror.Error{
		Code:       "upstream_invalid_request",
		Message:    "The upstream service rejected the request parameters.",
		Category:   "upstream",
		Source:     "upstream",
		HTTPStatus: http.StatusBadRequest,
		Version:    "v1",
	}
	task := store.GatewayTask{
		ID:           "task-parameter-error",
		Status:       "failed",
		ErrorCode:    "http_400",
		ErrorMessage: raw,
		PublicError:  &legacy,
		Attempts: []store.TaskAttempt{{
			AttemptNo:    1,
			Status:       "failed",
			StatusCode:   http.StatusBadRequest,
			ErrorCode:    "http_400",
			ErrorMessage: raw,
			PublicError:  &legacy,
		}},
	}

	public := publicGatewayTask(task)
	upstream := requireObject(t, public.PublicError.Details["upstreamError"])
	attemptUpstream := requireObject(t, public.Attempts[0].PublicError.Details["upstreamError"])
	if public.ErrorMessage != raw || public.Attempts[0].ErrorMessage != raw || upstream["message"] != raw || attemptUpstream["message"] != raw {
		t.Fatalf("task response did not forward safe upstream diagnostics: %+v", public)
	}
}

func TestTaskErrorHTTPStatusRebuildsLegacySnapshotFromAttempt(t *testing.T) {
	legacy := publicerror.Error{
		Code:       "upstream_request_rejected",
		HTTPStatus: http.StatusBadRequest,
		Version:    "v1",
	}
	task := store.GatewayTask{
		ErrorCode:   "http_422",
		PublicError: &legacy,
		Attempts:    []store.TaskAttempt{{StatusCode: http.StatusUnprocessableEntity}},
	}
	if got := taskErrorHTTPStatus(task); got != http.StatusUnprocessableEntity {
		t.Fatalf("taskErrorHTTPStatus = %d, want %d", got, http.StatusUnprocessableEntity)
	}
	public := publicTaskError(task)
	if public.Code != "upstream_unprocessable_request" || public.HTTPStatus != http.StatusUnprocessableEntity || public.Source != "upstream" {
		t.Fatalf("legacy public snapshot was not rebuilt: %+v", public)
	}
}
