package im

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

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"
)

func TestSnapshotDirectoryFlagsControlCoverageAndStoreReads(t *testing.T) {
	gin.SetMode(gin.TestMode)
	tests := []struct {
		name            string
		query           string
		wantChannels    bool
		wantDMChannels  bool
		wantChannelRead int
		wantDMRead      int
	}{
		{
			name:            "omitted defaults to both included",
			wantChannels:    true,
			wantDMChannels:  true,
			wantChannelRead: 1,
			wantDMRead:      1,
		},
		{
			name:           "both false omits both",
			query:          "?includeChannels=false&includeDMChannels=false",
			wantChannels:   false,
			wantDMChannels: false,
		},
		{
			name:           "DM only reads DM directory",
			query:          "?includeChannels=false&includeDMChannels=true",
			wantChannels:   false,
			wantDMChannels: true,
			wantDMRead:     1,
		},
		{
			name:            "values are trimmed and case insensitive",
			query:           "?includeChannels=%20TrUe%20&includeDMChannels=%20FaLsE%20",
			wantChannels:    true,
			wantDMChannels:  false,
			wantChannelRead: 1,
		},
	}

	for _, testCase := range tests {
		t.Run(testCase.name, func(t *testing.T) {
			channels := &countingSnapshotChannelStore{}
			dms := &countingSnapshotDMStore{}
			handler := &SnapshotHandler{channels: channels, dms: dms}
			request := imWorkspaceRequest()
			request.Gin.Request = httptest.NewRequest(http.MethodGet, "/"+testCase.query, nil)

			response, err := handler.Get(request)

			require.NoError(t, err)
			require.Equal(t, http.StatusOK, response.Status)
			require.Equal(t, testCase.wantChannelRead, channels.calls)
			require.Equal(t, testCase.wantDMRead, dms.calls)
			requireSnapshotDirectoryJSON(
				t,
				response.Body.(snapshotResponse),
				testCase.wantChannels,
				testCase.wantDMChannels,
			)
		})
	}
}

func TestSnapshotDirectoryFlagsRejectInvalidValuesBeforeStoreReads(t *testing.T) {
	gin.SetMode(gin.TestMode)
	tests := []struct {
		name        string
		query       string
		wantMessage string
	}{
		{
			name:        "empty channels value",
			query:       "?includeChannels=",
			wantMessage: "includeChannels must be true or false",
		},
		{
			name:        "unknown channels value",
			query:       "?includeChannels=yes",
			wantMessage: "includeChannels must be true or false",
		},
		{
			name:        "duplicate channels value",
			query:       "?includeChannels=true&includeChannels=false",
			wantMessage: "includeChannels must be specified at most once",
		},
		{
			name:        "empty DM channels value",
			query:       "?includeDMChannels=",
			wantMessage: "includeDMChannels must be true or false",
		},
		{
			name:        "unknown DM channels value",
			query:       "?includeDMChannels=yes",
			wantMessage: "includeDMChannels must be true or false",
		},
		{
			name:        "duplicate DM channels value",
			query:       "?includeDMChannels=true&includeDMChannels=false",
			wantMessage: "includeDMChannels must be specified at most once",
		},
	}

	for _, testCase := range tests {
		t.Run(testCase.name, func(t *testing.T) {
			channels := &countingSnapshotChannelStore{}
			dms := &countingSnapshotDMStore{}
			handler := &SnapshotHandler{channels: channels, dms: dms}
			request := imWorkspaceRequest()
			request.Gin.Request = httptest.NewRequest(http.MethodGet, "/"+testCase.query, nil)

			_, err := handler.Get(request)

			requireIMHTTPError(t, err, http.StatusBadRequest, testCase.wantMessage)
			require.Zero(t, channels.calls)
			require.Zero(t, dms.calls)
		})
	}
}

func TestSnapshotDMLimitValidationDependsOnDirectoryCoverage(t *testing.T) {
	gin.SetMode(gin.TestMode)

	t.Run("omitted DM directory ignores invalid limit", func(t *testing.T) {
		channels := &countingSnapshotChannelStore{}
		dms := &countingSnapshotDMStore{}
		handler := &SnapshotHandler{channels: channels, dms: dms}
		request := imWorkspaceRequest()
		request.Gin.Request = httptest.NewRequest(
			http.MethodGet,
			"/?includeDMChannels=false&dmLimit=invalid",
			nil,
		)

		response, err := handler.Get(request)

		require.NoError(t, err)
		require.Equal(t, 1, channels.calls)
		require.Zero(t, dms.calls)
		requireSnapshotDirectoryJSON(t, response.Body.(snapshotResponse), true, false)
	})

	for _, query := range []string{
		"?dmLimit=invalid",
		"?includeDMChannels=true&dmLimit=invalid",
	} {
		t.Run(query, func(t *testing.T) {
			channels := &countingSnapshotChannelStore{}
			dms := &countingSnapshotDMStore{}
			handler := &SnapshotHandler{channels: channels, dms: dms}
			request := imWorkspaceRequest()
			request.Gin.Request = httptest.NewRequest(http.MethodGet, "/"+query, nil)

			_, err := handler.Get(request)

			requireIMHTTPError(t, err, http.StatusBadRequest, "limit must be a positive integer")
			require.Zero(t, channels.calls)
			require.Zero(t, dms.calls)
		})
	}
}

func requireSnapshotDirectoryJSON(
	t *testing.T,
	body snapshotResponse,
	wantChannels bool,
	wantDMChannels bool,
) {
	t.Helper()
	encoded, err := json.Marshal(body)
	require.NoError(t, err)
	var payload struct {
		Channels   json.RawMessage `json:"channels"`
		DMChannels json.RawMessage `json:"dmChannels"`
		Coverage   *struct {
			Channels   bool `json:"channels"`
			DMChannels bool `json:"dmChannels"`
		} `json:"coverage"`
	}
	require.NoError(t, json.Unmarshal(encoded, &payload))
	require.Equal(t, json.RawMessage("[]"), payload.Channels)
	require.Equal(t, json.RawMessage("[]"), payload.DMChannels)
	if payload.Coverage == nil {
		require.Fail(t, "snapshot coverage is missing")
		return
	}
	require.Equal(t, wantChannels, payload.Coverage.Channels)
	require.Equal(t, wantDMChannels, payload.Coverage.DMChannels)
}
