package subsonic

import (
	"encoding/json"
	"encoding/xml"
	"io"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestWriteJSONEnvelope(t *testing.T) {
	rec := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/rest/ping.view", nil)
	Write(rec, req, PingResponse{Envelope: NewEnvelope("ok")})

	if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "application/json") {
		t.Errorf("content-type = %q", ct)
	}
	var payload map[string]map[string]any
	if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
		t.Fatalf("unmarshal: %v — body=%s", err, rec.Body)
	}
	inner, ok := payload["subsonic-response"]
	if !ok {
		t.Fatalf("missing subsonic-response key: %s", rec.Body)
	}
	if inner["status"] != "ok" || inner["version"] != APIVersion || inner["type"] != ServerType {
		t.Errorf("envelope fields = %+v", inner)
	}
	if inner["openSubsonic"] != true {
		t.Errorf("openSubsonic flag missing: %+v", inner)
	}
}

func TestWriteXMLEnvelope(t *testing.T) {
	rec := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/rest/getLicense.view?f=xml", nil)
	Write(rec, req, LicenseResponse{
		Envelope: NewEnvelope("ok"),
		License:  License{Valid: true},
	})

	if ct := rec.Header().Get("Content-Type"); !strings.HasPrefix(ct, "application/xml") {
		t.Errorf("content-type = %q", ct)
	}
	body, _ := io.ReadAll(rec.Body)
	var decoded struct {
		XMLName xml.Name `xml:"subsonic-response"`
		Status  string   `xml:"status,attr"`
		Version string   `xml:"version,attr"`
		License struct {
			Valid bool `xml:"valid,attr"`
		} `xml:"license"`
	}
	if err := xml.Unmarshal(body, &decoded); err != nil {
		t.Fatalf("xml decode: %v — body=%s", err, body)
	}
	if decoded.Status != "ok" || decoded.Version != APIVersion {
		t.Errorf("envelope attrs = %+v", decoded)
	}
	if !decoded.License.Valid {
		t.Errorf("license.valid attr not parsed: %+v", decoded)
	}
}

func TestWriteFailEnvelopeJSON(t *testing.T) {
	rec := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/rest/ping.view", nil)
	WriteFail(rec, req, ErrWrongCredentials, "nope")

	var payload map[string]map[string]any
	if err := json.Unmarshal(rec.Body.Bytes(), &payload); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}
	inner := payload["subsonic-response"]
	if inner["status"] != "failed" {
		t.Errorf("status = %v, want failed", inner["status"])
	}
	errMap, ok := inner["error"].(map[string]any)
	if !ok {
		t.Fatalf("error field missing/not object: %+v", inner)
	}
	if int(errMap["code"].(float64)) != ErrWrongCredentials {
		t.Errorf("error.code = %v, want %d", errMap["code"], ErrWrongCredentials)
	}
	if errMap["message"] != "nope" {
		t.Errorf("error.message = %v", errMap["message"])
	}
}

func TestWriteJSONPWrapsCallback(t *testing.T) {
	rec := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/rest/ping.view?f=jsonp&callback=cb", nil)
	Write(rec, req, PingResponse{Envelope: NewEnvelope("ok")})

	body := rec.Body.String()
	if !strings.HasPrefix(body, "cb(") || !strings.HasSuffix(strings.TrimSpace(body), ")") {
		t.Errorf("jsonp wrapper missing: %q", body)
	}
}
