package httpclient

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

func TestDefaultConfig(t *testing.T) {
	cfg := DefaultConfig()

	if cfg.Timeout != 30*time.Second {
		t.Errorf("expected Timeout to be 30s, got %v", cfg.Timeout)
	}
	if cfg.MaxIdleConns != 100 {
		t.Errorf("expected MaxIdleConns to be 100, got %d", cfg.MaxIdleConns)
	}
	if cfg.MaxIdleConnsPerHost != 10 {
		t.Errorf("expected MaxIdleConnsPerHost to be 10, got %d", cfg.MaxIdleConnsPerHost)
	}
	if cfg.IdleConnTimeout != 90*time.Second {
		t.Errorf("expected IdleConnTimeout to be 90s, got %v", cfg.IdleConnTimeout)
	}
	if cfg.DialTimeout != 10*time.Second {
		t.Errorf("expected DialTimeout to be 10s, got %v", cfg.DialTimeout)
	}
	if cfg.TLSHandshakeTimeout != 10*time.Second {
		t.Errorf("expected TLSHandshakeTimeout to be 10s, got %v", cfg.TLSHandshakeTimeout)
	}
}

func TestNew(t *testing.T) {
	t.Run("with nil config uses defaults", func(t *testing.T) {
		client := New(nil)
		if client == nil {
			t.Fatal("expected client to be non-nil")
		}
		if client.httpClient == nil {
			t.Fatal("expected httpClient to be non-nil")
		}
		if client.config.Timeout != 30*time.Second {
			t.Errorf("expected default timeout, got %v", client.config.Timeout)
		}
	})

	t.Run("with custom config", func(t *testing.T) {
		cfg := &Config{
			Timeout:             5 * time.Second,
			MaxIdleConns:        50,
			MaxIdleConnsPerHost: 5,
		}
		client := New(cfg)
		if client.config.Timeout != 5*time.Second {
			t.Errorf("expected timeout 5s, got %v", client.config.Timeout)
		}
		if client.config.MaxIdleConns != 50 {
			t.Errorf("expected MaxIdleConns 50, got %d", client.config.MaxIdleConns)
		}
	})
}

func TestClient_Get(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet {
			t.Errorf("expected GET method, got %s", r.Method)
		}
		if r.Header.Get("X-Custom-Header") != "test-value" {
			t.Errorf("expected custom header, got %s", r.Header.Get("X-Custom-Header"))
		}
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(`{"message": "success"}`))
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	headers := map[string]string{"X-Custom-Header": "test-value"}

	resp, err := client.Get(ctx, server.URL, headers)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer resp.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("expected status 200, got %d", resp.StatusCode)
	}
	if !resp.IsSuccess() {
		t.Error("expected IsSuccess to be true")
	}
}

func TestClient_Post(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			t.Errorf("expected POST method, got %s", r.Method)
		}
		body, _ := io.ReadAll(r.Body)
		if string(body) != "test body" {
			t.Errorf("expected body 'test body', got '%s'", string(body))
		}
		w.WriteHeader(http.StatusCreated)
		w.Write([]byte(`{"id": 123}`))
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	resp, err := client.Post(ctx, server.URL, strings.NewReader("test body"), nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer resp.Close()

	if resp.StatusCode != http.StatusCreated {
		t.Errorf("expected status 201, got %d", resp.StatusCode)
	}
}

func TestClient_PostJSON(t *testing.T) {
	type RequestBody struct {
		Name  string `json:"name"`
		Value int    `json:"value"`
	}

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			t.Errorf("expected POST method, got %s", r.Method)
		}
		if r.Header.Get("Content-Type") != "application/json" {
			t.Errorf("expected Content-Type application/json, got %s", r.Header.Get("Content-Type"))
		}

		var body RequestBody
		if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
			t.Errorf("failed to decode body: %v", err)
		}
		if body.Name != "test" || body.Value != 42 {
			t.Errorf("unexpected body: %+v", body)
		}

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	reqBody := RequestBody{Name: "test", Value: 42}
	resp, err := client.PostJSON(ctx, server.URL, reqBody, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	var result map[string]string
	if err := resp.ReadJSON(&result); err != nil {
		t.Fatalf("failed to read JSON: %v", err)
	}
	if result["status"] != "ok" {
		t.Errorf("expected status 'ok', got '%s'", result["status"])
	}
}

func TestClient_Put(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPut {
			t.Errorf("expected PUT method, got %s", r.Method)
		}
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	resp, err := client.Put(ctx, server.URL, strings.NewReader("update"), nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer resp.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("expected status 200, got %d", resp.StatusCode)
	}
}

func TestClient_PutJSON(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPut {
			t.Errorf("expected PUT method, got %s", r.Method)
		}
		if r.Header.Get("Content-Type") != "application/json" {
			t.Errorf("expected Content-Type application/json, got %s", r.Header.Get("Content-Type"))
		}
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	resp, err := client.PutJSON(ctx, server.URL, map[string]string{"key": "value"}, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer resp.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("expected status 200, got %d", resp.StatusCode)
	}
}

func TestClient_Delete(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodDelete {
			t.Errorf("expected DELETE method, got %s", r.Method)
		}
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	resp, err := client.Delete(ctx, server.URL, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer resp.Close()

	if resp.StatusCode != http.StatusNoContent {
		t.Errorf("expected status 204, got %d", resp.StatusCode)
	}
}

func TestClient_DeleteWithBody(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodDelete {
			t.Errorf("expected DELETE method, got %s", r.Method)
		}
		body, _ := io.ReadAll(r.Body)
		if string(body) != "delete body" {
			t.Errorf("expected body 'delete body', got '%s'", string(body))
		}
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	resp, err := client.DeleteWithBody(ctx, server.URL, strings.NewReader("delete body"), nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer resp.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("expected status 200, got %d", resp.StatusCode)
	}
}

func TestClient_Patch(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPatch {
			t.Errorf("expected PATCH method, got %s", r.Method)
		}
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	resp, err := client.Patch(ctx, server.URL, strings.NewReader("patch"), nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer resp.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("expected status 200, got %d", resp.StatusCode)
	}
}

func TestClient_PatchJSON(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPatch {
			t.Errorf("expected PATCH method, got %s", r.Method)
		}
		if r.Header.Get("Content-Type") != "application/json" {
			t.Errorf("expected Content-Type application/json, got %s", r.Header.Get("Content-Type"))
		}
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	resp, err := client.PatchJSON(ctx, server.URL, map[string]string{"key": "value"}, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer resp.Close()

	if resp.StatusCode != http.StatusOK {
		t.Errorf("expected status 200, got %d", resp.StatusCode)
	}
}

func TestResponse_ReadBody(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("response body"))
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	resp, err := client.Get(ctx, server.URL, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	body, err := resp.ReadBody()
	if err != nil {
		t.Fatalf("failed to read body: %v", err)
	}
	if string(body) != "response body" {
		t.Errorf("expected 'response body', got '%s'", string(body))
	}

	// Second read should fail
	_, err = resp.ReadBody()
	if err == nil {
		t.Error("expected error on second read")
	}
}

func TestResponse_ReadJSON(t *testing.T) {
	type ResponseData struct {
		Message string `json:"message"`
		Count   int    `json:"count"`
	}

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(ResponseData{Message: "hello", Count: 5})
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	resp, err := client.Get(ctx, server.URL, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	var data ResponseData
	if err := resp.ReadJSON(&data); err != nil {
		t.Fatalf("failed to read JSON: %v", err)
	}
	if data.Message != "hello" || data.Count != 5 {
		t.Errorf("unexpected data: %+v", data)
	}

	// Second read should fail
	if err := resp.ReadJSON(&data); err == nil {
		t.Error("expected error on second read")
	}
}

func TestResponse_Close(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("response body"))
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	resp, err := client.Get(ctx, server.URL, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Close should work
	if err := resp.Close(); err != nil {
		t.Errorf("unexpected error on close: %v", err)
	}

	// Second close should be safe
	if err := resp.Close(); err != nil {
		t.Errorf("unexpected error on second close: %v", err)
	}
}

func TestResponse_IsSuccess(t *testing.T) {
	tests := []struct {
		statusCode int
		expected   bool
	}{
		{200, true},
		{201, true},
		{204, true},
		{299, true},
		{300, false},
		{400, false},
		{404, false},
		{500, false},
	}

	for _, tt := range tests {
		t.Run(http.StatusText(tt.statusCode), func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.WriteHeader(tt.statusCode)
			}))
			defer server.Close()

			client := New(nil)
			defer client.Close()

			ctx := context.Background()
			resp, err := client.Get(ctx, server.URL, nil)
			if err != nil {
				t.Fatalf("unexpected error: %v", err)
			}
			defer resp.Close()

			if resp.IsSuccess() != tt.expected {
				t.Errorf("expected IsSuccess() = %v for status %d", tt.expected, tt.statusCode)
			}
		})
	}
}

func TestClient_ContextCancellation(t *testing.T) {
	// Create a server that delays response
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(2 * time.Second)
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	// Create a context that will be canceled
	ctx, cancel := context.WithCancel(context.Background())

	// Cancel the context after a short delay
	go func() {
		time.Sleep(100 * time.Millisecond)
		cancel()
	}()

	_, err := client.Get(ctx, server.URL, nil)
	if err == nil {
		t.Error("expected error due to context cancellation")
	}
	if !IsCanceled(err) {
		t.Errorf("expected canceled error, got: %v", err)
	}
}

func TestClient_Timeout(t *testing.T) {
	// Create a server that delays response
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(2 * time.Second)
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	// Create client with short timeout
	cfg := &Config{
		Timeout: 100 * time.Millisecond,
	}
	client := New(cfg)
	defer client.Close()

	ctx := context.Background()
	_, err := client.Get(ctx, server.URL, nil)
	if err == nil {
		t.Error("expected timeout error")
	}
	if !IsTimeout(err) {
		t.Errorf("expected timeout error, got: %v", err)
	}
}

func TestClient_ConnectionReuse(t *testing.T) {
	requestCount := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requestCount++
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()

	// Make multiple requests - they should reuse the connection
	for i := 0; i < 5; i++ {
		resp, err := client.Get(ctx, server.URL, nil)
		if err != nil {
			t.Fatalf("request %d failed: %v", i, err)
		}
		resp.Close()
	}

	if requestCount != 5 {
		t.Errorf("expected 5 requests, got %d", requestCount)
	}
}

func TestClient_GetHTTPClient(t *testing.T) {
	client := New(nil)
	defer client.Close()

	httpClient := client.GetHTTPClient()
	if httpClient == nil {
		t.Error("expected non-nil http.Client")
	}
}

func TestClient_GetConfig(t *testing.T) {
	cfg := &Config{
		Timeout:      5 * time.Second,
		MaxIdleConns: 50,
	}
	client := New(cfg)
	defer client.Close()

	gotCfg := client.GetConfig()
	if gotCfg.Timeout != 5*time.Second {
		t.Errorf("expected timeout 5s, got %v", gotCfg.Timeout)
	}
	if gotCfg.MaxIdleConns != 50 {
		t.Errorf("expected MaxIdleConns 50, got %d", gotCfg.MaxIdleConns)
	}
}

func TestClient_Headers(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Check multiple headers
		if r.Header.Get("Authorization") != "Bearer token123" {
			t.Errorf("expected Authorization header, got %s", r.Header.Get("Authorization"))
		}
		if r.Header.Get("X-Request-ID") != "req-123" {
			t.Errorf("expected X-Request-ID header, got %s", r.Header.Get("X-Request-ID"))
		}
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	headers := map[string]string{
		"Authorization": "Bearer token123",
		"X-Request-ID":  "req-123",
	}

	resp, err := client.Get(ctx, server.URL, headers)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer resp.Close()
}

func TestClient_ResponseHeaders(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("X-Custom-Response", "custom-value")
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := New(nil)
	defer client.Close()

	ctx := context.Background()
	resp, err := client.Get(ctx, server.URL, nil)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	defer resp.Close()

	if resp.Headers.Get("X-Custom-Response") != "custom-value" {
		t.Errorf("expected custom header, got %s", resp.Headers.Get("X-Custom-Response"))
	}
	if resp.Headers.Get("Content-Type") != "application/json" {
		t.Errorf("expected Content-Type header, got %s", resp.Headers.Get("Content-Type"))
	}
}
