package analytics

import (
	"context"
	"testing"

	"github.com/0xJacky/Nginx-UI/internal/nginx_log/searcher"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

func TestService_GetLogEntriesStats_Success(t *testing.T) {
	mockSearcher := &MockSearcher{}
	s := NewService(mockSearcher)

	ctx := context.Background()
	req := &searcher.SearchRequest{
		Limit:  100,
		Offset: 0,
	}

	expectedResult := &searcher.SearchResult{
		TotalHits: 5,
		Facets: map[string]*searcher.Facet{
			"status": {
				Terms: []*searcher.FacetTerm{
					{Term: "200", Count: 3},
					{Term: "404", Count: 1},
					{Term: "500", Count: 1},
				},
			},
			"method": {
				Terms: []*searcher.FacetTerm{
					{Term: "GET", Count: 4},
					{Term: "POST", Count: 1},
				},
			},
			"path_exact": {
				Terms: []*searcher.FacetTerm{
					{Term: "/a", Count: 3},
					{Term: "/b", Count: 1},
					{Term: "/c", Count: 1},
				},
			},
			"ip": {
				Terms: []*searcher.FacetTerm{
					{Term: "1.1.1.1", Count: 3},
					{Term: "1.1.1.2", Count: 1},
					{Term: "1.1.1.3", Count: 1},
				},
			},
			"user_agent": {
				Terms: []*searcher.FacetTerm{
					{Term: "Chrome", Count: 3},
					{Term: "Firefox", Count: 1},
					{Term: "Curl", Count: 1},
				},
			},
		},
	}

	mockSearcher.On("Search", ctx, mock.AnythingOfType("*searcher.SearchRequest")).Return(expectedResult, nil)

	result, err := s.GetLogEntriesStats(ctx, req)

	assert.NoError(t, err)
	assert.NotNil(t, result)

	// Check total entries
	assert.Equal(t, int64(5), result.TotalEntries)

	// Check status code distribution
	assert.Equal(t, 3, result.StatusCodeDist["200"])
	assert.Equal(t, 1, result.StatusCodeDist["404"])
	assert.Equal(t, 1, result.StatusCodeDist["500"])

	// Check method distribution
	assert.Equal(t, 4, result.MethodDist["GET"])
	assert.Equal(t, 1, result.MethodDist["POST"])

	// Check top paths
	assert.Len(t, result.TopPaths, 3)
	assert.Equal(t, "/a", result.TopPaths[0].Key)
	assert.Equal(t, 3, result.TopPaths[0].Value)

	// Check top IPs
	assert.Len(t, result.TopIPs, 3)
	assert.Equal(t, "1.1.1.1", result.TopIPs[0].Key)
	assert.Equal(t, 3, result.TopIPs[0].Value)

	// Check top user agents
	assert.Len(t, result.TopUserAgents, 3)
	assert.Equal(t, "Chrome", result.TopUserAgents[0].Key)
	assert.Equal(t, 3, result.TopUserAgents[0].Value)

	mockSearcher.AssertExpectations(t)
}

func TestService_GetLogEntriesStats_NilRequest(t *testing.T) {
	mockSearcher := &MockSearcher{}
	s := NewService(mockSearcher)

	ctx := context.Background()
	result, err := s.GetLogEntriesStats(ctx, nil)

	assert.Error(t, err)
	assert.Nil(t, result)
	assert.Contains(t, err.Error(), "request cannot be nil")

	mockSearcher.AssertExpectations(t)
}

func TestService_GetLogEntriesStats_SearchError(t *testing.T) {
	mockSearcher := &MockSearcher{}
	s := NewService(mockSearcher)

	ctx := context.Background()
	req := &searcher.SearchRequest{
		Limit:  100,
		Offset: 0,
	}

	mockSearcher.On("Search", ctx, mock.AnythingOfType("*searcher.SearchRequest")).Return(nil, assert.AnError)

	result, err := s.GetLogEntriesStats(ctx, req)

	assert.Error(t, err)
	assert.Nil(t, result)
	assert.Contains(t, err.Error(), "failed to search logs")

	mockSearcher.AssertExpectations(t)
}

func TestService_GetLogEntriesStats_NoFacets(t *testing.T) {
	mockSearcher := &MockSearcher{}
	s := NewService(mockSearcher)

	ctx := context.Background()
	req := &searcher.SearchRequest{
		Limit:  100,
		Offset: 0,
	}

	expectedResult := &searcher.SearchResult{
		TotalHits: 1000,
		Facets:    nil, // No facets
		Stats:     nil, // No stats
	}

	mockSearcher.On("Search", ctx, mock.AnythingOfType("*searcher.SearchRequest")).Return(expectedResult, nil)

	result, err := s.GetLogEntriesStats(ctx, req)

	assert.NoError(t, err)
	assert.NotNil(t, result)

	// Should have initialized empty maps and slices
	assert.Equal(t, int64(1000), result.TotalEntries)
	assert.NotNil(t, result.StatusCodeDist)
	assert.NotNil(t, result.MethodDist)
	assert.NotNil(t, result.TopPaths)
	assert.NotNil(t, result.TopIPs)
	assert.NotNil(t, result.TopUserAgents)
	
	// Should be empty
	assert.Len(t, result.StatusCodeDist, 0)
	assert.Len(t, result.MethodDist, 0)
	assert.Len(t, result.TopPaths, 0)
	assert.Len(t, result.TopIPs, 0)
	assert.Len(t, result.TopUserAgents, 0)

	// Stats should be nil
	assert.Nil(t, result.BytesStats)
	assert.Nil(t, result.ResponseTimeStats)

	mockSearcher.AssertExpectations(t)
}

func TestService_GetTopPaths_Success(t *testing.T) {
	mockSearcher := &MockSearcher{}
	s := NewService(mockSearcher)

	ctx := context.Background()
	req := &TopListRequest{
		StartTime: 1000,
		EndTime:   2000,
		Limit:     5,
		Field:     FieldPath,
		LogPath:   "/var/log/nginx/access.log",
	}

	expectedResult := &searcher.SearchResult{
		TotalHits: 4,
		Facets: map[string]*searcher.Facet{
			"path_exact": {
				Terms: []*searcher.FacetTerm{
					{Term: "/a", Count: 2},
					{Term: "/b", Count: 1},
					{Term: "/c", Count: 1},
				},
			},
		},
	}

	mockSearcher.On("Search", ctx, mock.AnythingOfType("*searcher.SearchRequest")).Return(expectedResult, nil)

	result, err := s.GetTopPaths(ctx, req)

	assert.NoError(t, err)
	assert.NotNil(t, result)
	assert.Len(t, result, 3)

	// Should be ordered by count descending
	assert.Equal(t, "/a", result[0].Key)
	assert.Equal(t, 2, result[0].Value)
	assert.Equal(t, "/b", result[1].Key)
	assert.Equal(t, 1, result[1].Value)

	mockSearcher.AssertExpectations(t)
}

func TestService_GetTopPaths_WithLimit(t *testing.T) {
	mockSearcher := &MockSearcher{}
	s := NewService(mockSearcher)

	ctx := context.Background()
	req := &TopListRequest{
		StartTime: 1000,
		EndTime:   2000,
		Limit:     1,
		Field:     FieldPath,
		LogPath:   "/var/log/nginx/access.log",
	}

	expectedResult := &searcher.SearchResult{
		TotalHits: 4,
		Facets: map[string]*searcher.Facet{
			"path_exact": {
				Terms: []*searcher.FacetTerm{
					{Term: "/a", Count: 2},
				},
			},
		},
	}

	mockSearcher.On("Search", ctx, mock.AnythingOfType("*searcher.SearchRequest")).Return(expectedResult, nil)

	result, err := s.GetTopPaths(ctx, req)

	assert.NoError(t, err)
	assert.NotNil(t, result)
	assert.Len(t, result, 1) // Limited to 1

	assert.Equal(t, "/a", result[0].Key)
	assert.Equal(t, 2, result[0].Value)

	mockSearcher.AssertExpectations(t)
}

func TestService_GetTopIPs_Success(t *testing.T) {
	mockSearcher := &MockSearcher{}
	s := NewService(mockSearcher)

	ctx := context.Background()
	req := &TopListRequest{
		StartTime: 1000,
		EndTime:   2000,
		Limit:     3,
		Field:     FieldIP,
		LogPath:   "/var/log/nginx/access.log",
	}

	expectedResult := &searcher.SearchResult{
		TotalHits: 3,
		Facets: map[string]*searcher.Facet{
			"ip": {
				Terms: []*searcher.FacetTerm{
					{Term: "1.1.1.1", Count: 2},
					{Term: "1.1.1.2", Count: 1},
				},
			},
		},
	}

	mockSearcher.On("Search", ctx, mock.AnythingOfType("*searcher.SearchRequest")).Return(expectedResult, nil)

	result, err := s.GetTopIPs(ctx, req)

	assert.NoError(t, err)
	assert.NotNil(t, result)
	assert.Len(t, result, 2)

	assert.Equal(t, "1.1.1.1", result[0].Key)
	assert.Equal(t, 2, result[0].Value)
	assert.Equal(t, "1.1.1.2", result[1].Key)
	assert.Equal(t, 1, result[1].Value)

	mockSearcher.AssertExpectations(t)
}

func TestService_GetTopUserAgents_Success(t *testing.T) {
	mockSearcher := &MockSearcher{}
	s := NewService(mockSearcher)

	ctx := context.Background()
	req := &TopListRequest{
		StartTime: 1000,
		EndTime:   2000,
		Limit:     2,
		Field:     FieldUserAgent,
		LogPath:   "/var/log/nginx/access.log",
	}

	expectedResult := &searcher.SearchResult{
		TotalHits: 2,
		Facets: map[string]*searcher.Facet{
			"user_agent": {
				Terms: []*searcher.FacetTerm{
					{Term: "Chrome", Count: 1},
					{Term: "Firefox", Count: 1},
				},
			},
		},
	}

	mockSearcher.On("Search", ctx, mock.AnythingOfType("*searcher.SearchRequest")).Return(expectedResult, nil)

	result, err := s.GetTopUserAgents(ctx, req)

	assert.NoError(t, err)
	assert.NotNil(t, result)
	assert.Len(t, result, 2)

	assert.Equal(t, "Chrome", result[0].Key)
	assert.Equal(t, 1, result[0].Value)
	assert.Equal(t, "Firefox", result[1].Key)
	assert.Equal(t, 1, result[1].Value)

	mockSearcher.AssertExpectations(t)
}

func TestService_GetTopPaths_InvalidTimeRange(t *testing.T) {
	mockSearcher := &MockSearcher{}
	s := NewService(mockSearcher)

	ctx := context.Background()
	req := &TopListRequest{
		StartTime: 2000,
		EndTime:   1000, // End before start
		Limit:     10,
		Field:     FieldPath,
		LogPath:   "/var/log/nginx/access.log",
	}

	result, err := s.GetTopPaths(ctx, req)

	assert.Error(t, err)
	assert.Nil(t, result)
	assert.Contains(t, err.Error(), "time")

	mockSearcher.AssertExpectations(t)
}

func TestService_GetTopPaths_NoFacets(t *testing.T) {
	mockSearcher := &MockSearcher{}
	s := NewService(mockSearcher)

	ctx := context.Background()
	req := &TopListRequest{
		StartTime: 1000,
		EndTime:   2000,
		Limit:     10,
		Field:     FieldPath,
	}

	expectedResult := &searcher.SearchResult{
		TotalHits: 1000,
		Facets:    nil, // No facets
	}

	mockSearcher.On("Search", ctx, mock.AnythingOfType("*searcher.SearchRequest")).Return(expectedResult, nil)

	result, err := s.GetTopPaths(ctx, req)

	assert.NoError(t, err)
	assert.NotNil(t, result)
	assert.Len(t, result, 0) // Should be empty

	mockSearcher.AssertExpectations(t)
}

func TestService_GetTopPaths_DefaultLimit(t *testing.T) {
	mockSearcher := &MockSearcher{}
	s := NewService(mockSearcher)

	ctx := context.Background()
	req := &TopListRequest{
		StartTime: 1000,
		EndTime:   2000,
		Limit:     0, // Should use default
		Field:     FieldPath,
	}

	expectedResult := &searcher.SearchResult{
		TotalHits: 1000,
		Facets: map[string]*searcher.Facet{
			"path_exact": {
				Field: "path_exact",
				Total: 1000,
				Terms: []*searcher.FacetTerm{
					{Term: "/api/users", Count: 400},
				},
			},
		},
	}

	mockSearcher.On("Search", ctx, mock.AnythingOfType("*searcher.SearchRequest")).Return(expectedResult, nil)

	result, err := s.GetTopPaths(ctx, req)

	assert.NoError(t, err)
	assert.NotNil(t, result)
	// Should work with default limit

	mockSearcher.AssertExpectations(t)
}

func TestService_GetTopPaths_MaxLimit(t *testing.T) {
	mockSearcher := &MockSearcher{}
	s := NewService(mockSearcher)

	ctx := context.Background()
	req := &TopListRequest{
		StartTime: 1000,
		EndTime:   2000,
		Limit:     99999, // Should be capped to MaxLimit
		Field:     FieldPath,
	}

	expectedResult := &searcher.SearchResult{
		TotalHits: 1000,
		Facets: map[string]*searcher.Facet{
			"path_exact": {
				Field: "path_exact",
				Total: 1000,
				Terms: []*searcher.FacetTerm{
					{Term: "/api/users", Count: 400},
				},
			},
		},
	}

	mockSearcher.On("Search", ctx, mock.AnythingOfType("*searcher.SearchRequest")).Return(expectedResult, nil)

	result, err := s.GetTopPaths(ctx, req)

	assert.NoError(t, err)
	assert.NotNil(t, result)
	// Should work with capped limit

	mockSearcher.AssertExpectations(t)
}