package push

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// newTestFCMClient creates an FCMClient whose endpoint points at the given test
// server URL. The HTTP client uses no OAuth transport so tests can run without
// real Google credentials.
func newTestFCMClient(serverURL string) *FCMClient {
	return &FCMClient{
		projectID:  "test-project",
		endpoint:   serverURL,
		httpClient: &http.Client{},
	}
}

// serveFCMV1Success creates a test server that returns a successful v1 response
// for every request.
func serveFCMV1Success(t *testing.T) *httptest.Server {
	t.Helper()
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Verify it looks like a v1 request.
		var req fcmV1Request
		err := json.NewDecoder(r.Body).Decode(&req)
		require.NoError(t, err)
		require.NotNil(t, req.Message)

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		resp := fcmV1Response{Name: "projects/test-project/messages/0:12345"}
		_ = json.NewEncoder(w).Encode(resp)
	}))
}

// serveFCMV1Error creates a test server that returns the given status code and
// a structured v1 error response for every request.
func serveFCMV1Error(t *testing.T, statusCode int, errStatus string, errMessage string) *httptest.Server {
	t.Helper()
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(statusCode)
		resp := fcmV1ErrorResponse{
			Error: fcmV1Error{
				Code:    statusCode,
				Message: errMessage,
				Status:  errStatus,
			},
		}
		_ = json.NewEncoder(w).Encode(resp)
	}))
}

func TestFCMV1Send_Success_SingleToken(t *testing.T) {
	server := serveFCMV1Success(t)
	defer server.Close()

	client := newTestFCMClient(server.URL)
	err := client.Send(context.Background(), []string{"token-aaa-111"}, "Title", "Body", nil)
	assert.NoError(t, err)
}

func TestFCMV1Send_Success_MultipleTokens(t *testing.T) {
	var mu sync.Mutex
	receivedTokens := make([]string, 0)

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var req fcmV1Request
		_ = json.NewDecoder(r.Body).Decode(&req)

		mu.Lock()
		receivedTokens = append(receivedTokens, req.Message.Token)
		mu.Unlock()

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		resp := fcmV1Response{Name: "projects/test-project/messages/0:12345"}
		_ = json.NewEncoder(w).Encode(resp)
	}))
	defer server.Close()

	client := newTestFCMClient(server.URL)
	tokens := []string{"token-aaa-111", "token-bbb-222", "token-ccc-333"}

	err := client.Send(context.Background(), tokens, "Title", "Body", map[string]string{"key": "value"})
	assert.NoError(t, err)
	assert.ElementsMatch(t, tokens, receivedTokens)
}

func TestFCMV1Send_EmptyTokens_ReturnsNil(t *testing.T) {
	client := &FCMClient{
		projectID:  "test-project",
		endpoint:   "http://unused",
		httpClient: &http.Client{},
	}

	err := client.Send(context.Background(), []string{}, "Title", "Body", nil)
	assert.NoError(t, err)
}

func TestFCMV1Send_AllFail_ReturnsError(t *testing.T) {
	server := serveFCMV1Error(t, http.StatusNotFound, "UNREGISTERED", "The registration token is not registered")
	defer server.Close()

	client := newTestFCMClient(server.URL)
	tokens := []string{"bad-token-1", "bad-token-2"}

	err := client.Send(context.Background(), tokens, "Title", "Body", nil)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "all FCM notifications failed")
}

func TestFCMV1Send_PartialFailure_ReturnsNil(t *testing.T) {
	var mu sync.Mutex
	callCount := 0

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		mu.Lock()
		callCount++
		n := callCount
		mu.Unlock()

		w.Header().Set("Content-Type", "application/json")
		if n == 1 {
			// First token succeeds
			w.WriteHeader(http.StatusOK)
			resp := fcmV1Response{Name: "projects/test-project/messages/0:12345"}
			_ = json.NewEncoder(w).Encode(resp)
		} else {
			// Second token fails
			w.WriteHeader(http.StatusNotFound)
			resp := fcmV1ErrorResponse{
				Error: fcmV1Error{Code: 404, Message: "not registered", Status: "UNREGISTERED"},
			}
			_ = json.NewEncoder(w).Encode(resp)
		}
	}))
	defer server.Close()

	client := newTestFCMClient(server.URL)
	tokens := []string{"good-token", "bad-token"}

	// Partial failure: at least one succeeded, so no error returned.
	err := client.Send(context.Background(), tokens, "Title", "Body", nil)
	assert.NoError(t, err)
}

func TestFCMV1Send_UnregisteredError(t *testing.T) {
	server := serveFCMV1Error(t, http.StatusNotFound, "UNREGISTERED", "The registration token is not registered")
	defer server.Close()

	client := newTestFCMClient(server.URL)

	err := client.sendOne(context.Background(), "stale-token", "Title", "Body", nil)
	require.Error(t, err)

	var sendErr *FCMSendError
	require.ErrorAs(t, err, &sendErr)
	assert.True(t, sendErr.IsUnregistered())
	assert.Equal(t, FCMErrUnregistered, sendErr.ErrorCode)
	assert.Equal(t, http.StatusNotFound, sendErr.StatusCode)
}

func TestFCMV1Send_QuotaExceededError(t *testing.T) {
	server := serveFCMV1Error(t, http.StatusTooManyRequests, "QUOTA_EXCEEDED", "Sending quota exceeded")
	defer server.Close()

	client := newTestFCMClient(server.URL)

	err := client.sendOne(context.Background(), "some-token", "Title", "Body", nil)
	require.Error(t, err)

	var sendErr *FCMSendError
	require.ErrorAs(t, err, &sendErr)
	assert.Equal(t, FCMErrQuotaExceeded, sendErr.ErrorCode)
	assert.Equal(t, http.StatusTooManyRequests, sendErr.StatusCode)
}

func TestFCMV1Send_UnparseableErrorResponse(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte("not json at all"))
	}))
	defer server.Close()

	client := newTestFCMClient(server.URL)

	err := client.sendOne(context.Background(), "some-token", "Title", "Body", nil)
	require.Error(t, err)

	var sendErr *FCMSendError
	require.ErrorAs(t, err, &sendErr)
	assert.Equal(t, http.StatusInternalServerError, sendErr.StatusCode)
	assert.Contains(t, sendErr.Message, "unparseable error response")
}

func TestFCMV1Send_RequestPayloadFormat(t *testing.T) {
	var receivedReq fcmV1Request

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Verify Content-Type header
		assert.Equal(t, "application/json", r.Header.Get("Content-Type"))
		assert.Equal(t, http.MethodPost, r.Method)

		err := json.NewDecoder(r.Body).Decode(&receivedReq)
		require.NoError(t, err)

		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		_ = json.NewEncoder(w).Encode(fcmV1Response{Name: "projects/test-project/messages/0:12345"})
	}))
	defer server.Close()

	client := newTestFCMClient(server.URL)
	data := map[string]string{"task_id": "42", "action": "complete"}
	err := client.Send(context.Background(), []string{"device-token-xyz"}, "Task Due", "Your task is due today", data)
	require.NoError(t, err)

	// Verify the v1 message structure
	require.NotNil(t, receivedReq.Message)
	assert.Equal(t, "device-token-xyz", receivedReq.Message.Token)
	assert.Equal(t, "Task Due", receivedReq.Message.Notification.Title)
	assert.Equal(t, "Your task is due today", receivedReq.Message.Notification.Body)
	assert.Equal(t, "42", receivedReq.Message.Data["task_id"])
	assert.Equal(t, "complete", receivedReq.Message.Data["action"])
	assert.Equal(t, "HIGH", receivedReq.Message.Android.Priority)
}

func TestFCMSendError_Error(t *testing.T) {
	sendErr := &FCMSendError{
		Token:      "abcdef1234567890",
		StatusCode: 404,
		ErrorCode:  FCMErrUnregistered,
		Message:    "token not registered",
	}

	errStr := sendErr.Error()
	assert.Contains(t, errStr, "abcdef12...")
	assert.Contains(t, errStr, "token not registered")
	assert.Contains(t, errStr, "404")
	assert.Contains(t, errStr, "UNREGISTERED")
}

func TestFCMSendError_IsUnregistered(t *testing.T) {
	tests := []struct {
		name     string
		code     FCMErrorCode
		expected bool
	}{
		{"unregistered", FCMErrUnregistered, true},
		{"quota_exceeded", FCMErrQuotaExceeded, false},
		{"internal", FCMErrInternal, false},
		{"empty", "", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := &FCMSendError{ErrorCode: tt.code}
			assert.Equal(t, tt.expected, err.IsUnregistered())
		})
	}
}
