package daemon

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

	"github.com/openeuler/Conch/internal/apperror"
	"github.com/openeuler/Conch/pkg/ulog"
)

type recordingAPILogger struct {
	messages []string
	fields   [][]ulog.Field
}

func (l *recordingAPILogger) Debug(string, ...ulog.Field) {}
func (l *recordingAPILogger) Info(string, ...ulog.Field)  {}
func (l *recordingAPILogger) Warn(string, ...ulog.Field)  {}
func (l *recordingAPILogger) Fatal(string, ...ulog.Field) {}
func (l *recordingAPILogger) Error(message string, fields ...ulog.Field) {
	l.messages = append(l.messages, message)
	l.fields = append(l.fields, append([]ulog.Field(nil), fields...))
}
func (l *recordingAPILogger) With(...ulog.Field) ulog.Logger               { return l }
func (l *recordingAPILogger) WithContext(context.Context) ulog.Logger      { return l }
func (l *recordingAPILogger) ReplaceField(string, interface{}) ulog.Logger { return l }

func TestHTTPStatusForErrorKind(t *testing.T) {
	tests := []struct {
		kind apperror.Kind
		want int
	}{
		{apperror.Internal, http.StatusInternalServerError},
		{apperror.InvalidArgument, http.StatusBadRequest},
		{apperror.Unauthenticated, http.StatusUnauthorized},
		{apperror.PermissionDenied, http.StatusForbidden},
		{apperror.NotFound, http.StatusNotFound},
		{apperror.AlreadyExists, http.StatusConflict},
		{apperror.Conflict, http.StatusConflict},
		{apperror.FailedPrecondition, http.StatusConflict},
		{apperror.ResourceExhausted, http.StatusTooManyRequests},
		{apperror.PayloadTooLarge, http.StatusRequestEntityTooLarge},
		{apperror.UpstreamFailure, http.StatusBadGateway},
		{apperror.Unavailable, http.StatusServiceUnavailable},
		{apperror.DeadlineExceeded, http.StatusGatewayTimeout},
		{apperror.NotImplemented, http.StatusNotImplemented},
		{apperror.Kind(255), http.StatusInternalServerError},
	}
	for _, tt := range tests {
		if got := httpStatusForErrorKind(tt.kind); got != tt.want {
			t.Errorf("httpStatusForErrorKind(%d) = %d, want %d", tt.kind, got, tt.want)
		}
	}
}

func TestWriteAPIErrorDoesNotExposeCause(t *testing.T) {
	prototype := apperror.Define(apperror.InvalidArgument, "test.invalid_argument", "safe public message")
	recorder := httptest.NewRecorder()
	writeAPIError(recorder, prototype.Wrap(errors.New("secret internal path /srv/private")))

	if recorder.Code != http.StatusBadRequest {
		t.Fatalf("status = %d, body = %s", recorder.Code, recorder.Body.String())
	}
	if got := recorder.Header().Get("Content-Type"); got != "application/json" {
		t.Fatalf("Content-Type = %q", got)
	}
	want := "{\"status\":\"error\",\"code\":\"test.invalid_argument\",\"error\":\"safe public message\"}\n"
	if recorder.Body.String() != want {
		t.Fatalf("body = %q, want %q", recorder.Body.String(), want)
	}
}

func TestWriteAPIErrorUsesSafeFallback(t *testing.T) {
	previousLogger := ulog.GetLogger()
	logger := &recordingAPILogger{}
	ulog.SetLogger(logger)
	t.Cleanup(func() { ulog.SetLogger(previousLogger) })

	cause := errors.New("database password and private path")
	recorder := httptest.NewRecorder()
	writeAPIError(recorder, cause, ulog.F("sandbox_id", "sandbox-1"))
	if recorder.Code != http.StatusInternalServerError || recorder.Body.String() != "{\"status\":\"error\",\"code\":\"internal\",\"error\":\"internal server error\"}\n" {
		t.Fatalf("response = status:%d body:%q", recorder.Code, recorder.Body.String())
	}
	if len(logger.messages) != 1 || logger.messages[0] != "API request failed" {
		t.Fatalf("error logs = %#v, want one API failure log", logger.messages)
	}
	if got := fieldValue(logger.fields[0], "error"); got != cause {
		t.Fatalf("logged error = %v, want original cause", got)
	}
	if got := fieldValue(logger.fields[0], "sandbox_id"); got != "sandbox-1" {
		t.Fatalf("logged sandbox_id = %v, want sandbox-1", got)
	}
}

func fieldValue(fields []ulog.Field, key string) interface{} {
	for _, field := range fields {
		if field.Key == key {
			return field.Value
		}
	}
	return nil
}

func TestClassifyAPIErrorUsesOutermostApplicationError(t *testing.T) {
	inner := apperror.Define(apperror.NotFound, "inner.not_found", "inner not found")
	outer := apperror.Define(apperror.Conflict, "outer.conflict", "outer conflict")
	status, response := classifyAPIError(outer.Wrap(inner.New()))
	if status != http.StatusConflict || response.Code != "outer.conflict" || response.Error != "outer conflict" {
		t.Fatalf("classification = status:%d response:%#v", status, response)
	}
}

func TestClassifyAPIErrorRejectsIncompleteApplicationError(t *testing.T) {
	status, response := classifyAPIError(&apperror.Error{})
	if status != http.StatusInternalServerError || response != internalAPIError {
		t.Fatalf("classification = status:%d response:%#v", status, response)
	}
}