package services

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

	"Noooste/garage-ui/internal/config"
	"Noooste/garage-ui/internal/models"
	logpkg "Noooste/garage-ui/pkg/logger"

	"github.com/rs/zerolog"
)

// newAdminWithServer creates a GarageAdminService pointed at a test server
// and returns it with a cleanup hook.
func newAdminWithServer(t *testing.T, handler http.HandlerFunc) *GarageAdminService {
	t.Helper()
	srv := httptest.NewServer(handler)
	t.Cleanup(srv.Close)
	cfg := &config.GarageConfig{
		AdminEndpoint: srv.URL,
		AdminToken:    "test-token",
	}
	return NewGarageAdminService(cfg, "info")
}

// ctxWithBufferLogger attaches a zerolog.Logger writing to buf onto ctx.
func ctxWithBufferLogger(buf *bytes.Buffer) context.Context {
	l := zerolog.New(buf)
	return logpkg.IntoCtx(context.Background(), l)
}

func parseJSONLines(t *testing.T, s string) []map[string]any {
	t.Helper()
	out := []map[string]any{}
	for _, line := range strings.Split(s, "\n") {
		if strings.TrimSpace(line) == "" {
			continue
		}
		var m map[string]any
		if err := json.Unmarshal([]byte(line), &m); err != nil {
			t.Fatalf("not JSON: %v — %s", err, line)
		}
		out = append(out, m)
	}
	return out
}

func TestCreateBucket_SuccessLogsInfoWithFields(t *testing.T) {
	admin := newAdminWithServer(t, func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		_ = json.NewEncoder(w).Encode(models.GarageBucketInfo{ID: "bkt-123"})
	})

	var buf bytes.Buffer
	ctx := ctxWithBufferLogger(&buf)

	alias := "my-bucket"
	if _, err := admin.CreateBucket(ctx, models.CreateBucketAdminRequest{GlobalAlias: &alias}); err != nil {
		t.Fatalf("CreateBucket: %v", err)
	}

	lines := parseJSONLines(t, buf.String())
	if len(lines) < 2 {
		t.Fatalf("expected >=2 lines (start + success), got %d: %s", len(lines), buf.String())
	}

	final := lines[len(lines)-1]
	if final["component"] != "admin" {
		t.Errorf("component = %v, want admin", final["component"])
	}
	if final["operation"] != "create_bucket" {
		t.Errorf("operation = %v, want create_bucket", final["operation"])
	}
	if final["outcome"] != "success" {
		t.Errorf("outcome = %v, want success", final["outcome"])
	}
	if final["level"] != "info" {
		t.Errorf("level = %v, want info", final["level"])
	}
	if _, ok := final["duration_ms"].(float64); !ok {
		t.Errorf("missing duration_ms: %v", final)
	}
	if final["bucket_id"] != "bkt-123" {
		t.Errorf("bucket_id = %v, want bkt-123", final["bucket_id"])
	}
}

func TestCreateBucket_FailureLogsError(t *testing.T) {
	admin := newAdminWithServer(t, func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusConflict)
		_, _ = w.Write([]byte(`{"error":"already exists"}`))
	})

	var buf bytes.Buffer
	ctx := ctxWithBufferLogger(&buf)

	alias := "duplicate"
	_, err := admin.CreateBucket(ctx, models.CreateBucketAdminRequest{GlobalAlias: &alias})
	if err == nil {
		t.Fatal("expected error, got nil")
	}

	lines := parseJSONLines(t, buf.String())
	var failure map[string]any
	for _, line := range lines {
		if line["level"] == "error" {
			failure = line
			break
		}
	}
	if failure == nil {
		t.Fatalf("no error-level log line: %s", buf.String())
	}

	if failure["outcome"] != "failure" {
		t.Errorf("outcome = %v, want failure", failure["outcome"])
	}
	if failure["operation"] != "create_bucket" {
		t.Errorf("operation = %v, want create_bucket", failure["operation"])
	}
	if _, ok := failure["error"].(string); !ok {
		t.Errorf("missing error field: %v", failure)
	}
	if _, ok := failure["duration_ms"].(float64); !ok {
		t.Errorf("missing duration_ms: %v", failure)
	}
}
