package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http/httptest"
	"testing"

	"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
)

func decodeEnvelope(t *testing.T, body []byte) errorBody {
	t.Helper()
	var env errorBody
	if err := json.Unmarshal(body, &env); err != nil {
		t.Fatalf("decode envelope: %v (body=%s)", err, string(body))
	}
	return env
}

func TestWriteErr_APIError(t *testing.T) {
	rec := httptest.NewRecorder()
	writeErr(rec, apierror.NotFound("track"))

	if rec.Code != 404 {
		t.Fatalf("status: want 404, got %d", rec.Code)
	}
	env := decodeEnvelope(t, rec.Body.Bytes())
	if env.Error.Code != "track_not_found" {
		t.Errorf("code: want track_not_found, got %q", env.Error.Code)
	}
	if env.Error.Message != "track not found" {
		t.Errorf("message: want %q, got %q", "track not found", env.Error.Message)
	}
}

func TestWriteErr_Sentinel(t *testing.T) {
	rec := httptest.NewRecorder()
	writeErr(rec, apierror.ErrUnauthorized)

	if rec.Code != 401 {
		t.Fatalf("status: want 401, got %d", rec.Code)
	}
	env := decodeEnvelope(t, rec.Body.Bytes())
	if env.Error.Code != "unauthorized" {
		t.Errorf("code: want unauthorized, got %q", env.Error.Code)
	}
	if env.Error.Message != "authentication required" {
		t.Errorf("message: want %q, got %q", "authentication required", env.Error.Message)
	}
}

func TestWriteErr_PlainError(t *testing.T) {
	rec := httptest.NewRecorder()
	writeErr(rec, errors.New("boom"))

	if rec.Code != 500 {
		t.Fatalf("status: want 500, got %d", rec.Code)
	}
	env := decodeEnvelope(t, rec.Body.Bytes())
	if env.Error.Code != "server_error" {
		t.Errorf("code: want server_error, got %q", env.Error.Code)
	}
	if env.Error.Message != "internal server error" {
		t.Errorf("message: want %q, got %q", "internal server error", env.Error.Message)
	}
}

func TestWriteErr_WrappedAPIError(t *testing.T) {
	rec := httptest.NewRecorder()
	writeErr(rec, fmt.Errorf("ctx: %w", apierror.BadRequest("bad", "no good")))

	if rec.Code != 400 {
		t.Fatalf("status: want 400, got %d", rec.Code)
	}
	env := decodeEnvelope(t, rec.Body.Bytes())
	if env.Error.Code != "bad" {
		t.Errorf("code: want bad, got %q", env.Error.Code)
	}
	if env.Error.Message != "no good" {
		t.Errorf("message: want %q, got %q", "no good", env.Error.Message)
	}
}

func TestWriteErr_EnvelopeShape(t *testing.T) {
	rec := httptest.NewRecorder()
	writeErr(rec, apierror.BadRequest("x", "y"))

	var raw map[string]any
	if err := json.Unmarshal(rec.Body.Bytes(), &raw); err != nil {
		t.Fatalf("decode raw: %v", err)
	}
	if len(raw) != 1 {
		t.Fatalf("top-level keys: want 1, got %d (%v)", len(raw), raw)
	}
	inner, ok := raw["error"].(map[string]any)
	if !ok {
		t.Fatalf("error: want object, got %T", raw["error"])
	}
	if len(inner) != 2 {
		t.Fatalf("inner keys: want 2, got %d (%v)", len(inner), inner)
	}
	if _, ok := inner["code"]; !ok {
		t.Errorf("inner.code missing")
	}
	if _, ok := inner["message"]; !ok {
		t.Errorf("inner.message missing")
	}
}

func TestWriteErr_ContentType(t *testing.T) {
	rec := httptest.NewRecorder()
	writeErr(rec, apierror.BadRequest("x", "y"))

	if got := rec.Header().Get("Content-Type"); got != "application/json" {
		t.Errorf("Content-Type: want application/json, got %q", got)
	}
}
