package rest_test

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

	"github.com/gin-gonic/gin"
	analyticsfeature "github.com/nexuscrm/backend/internal/features/analytics"
	"github.com/nexuscrm/backend/internal/interfaces/rest"
	"github.com/nexuscrm/backend/pkg/auth"
	"github.com/nexuscrm/shared/pkg/api"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func init() {
	gin.SetMode(gin.TestMode)
}

type executeReportDefinitionQueryService struct {
	lastQuery api.QueryRequest
}

func (s *executeReportDefinitionQueryService) Query(_ context.Context, req api.QueryRequest, _ *models.UserSession) (*models.QueryResult, error) {
	s.lastQuery = req
	return &models.QueryResult{
		Records: []models.SObject{
			{"Id": "001", "Name": "Acme"},
		},
	}, nil
}

func (s *executeReportDefinitionQueryService) reset() {
	s.lastQuery = api.QueryRequest{}
}

func (s *executeReportDefinitionQueryService) criteria() []api.QueryCriterion {
	return s.lastQuery.Criteria
}

func (s *executeReportDefinitionQueryService) hasCriterion(field string, op string, val interface{}) bool {
	for _, criterion := range s.lastQuery.Criteria {
		if criterion.Field == field && criterion.Op == op && criterion.Val == val {
			return true
		}
	}
	return false
}

func (s *executeReportDefinitionQueryService) QueryWithFilter(_ context.Context, _ string, _ string, _ *models.UserSession, _ string, _ string, _ int) (*models.QueryResult, error) {
	return nil, nil
}

func (s *executeReportDefinitionQueryService) RunAnalytics(_ context.Context, _ models.AnalyticsQuery, _ *models.UserSession) (interface{}, error) {
	return nil, nil
}

func (s *executeReportDefinitionQueryService) SearchSingleObject(_ context.Context, _ string, _ string, _ []string, _ []string, _ int, _ int, _ string, _ string, _ string, _ *models.UserSession) ([]models.SObject, error) {
	return nil, nil
}

func (s *executeReportDefinitionQueryService) GlobalSearch(_ context.Context, _ string, _ *models.UserSession) ([]models.SearchResult, error) {
	return nil, nil
}

func (s *executeReportDefinitionQueryService) Calculate(_ context.Context, _ string, _ models.SObject, _ *models.UserSession) (models.SObject, error) {
	return nil, nil
}

func (s *executeReportDefinitionQueryService) ExecuteRawSQL(_ context.Context, _ string, _ []interface{}, _ *models.UserSession) ([]models.SObject, error) {
	return nil, nil
}

// ---------------------------------------------------------------------------
// extractReportType — exported via test-only accessor (function is unexported,
// but we test the handler behaviour that uses it via CreateReport)
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// AnalyticsHandler.RunReport — input validation
// ---------------------------------------------------------------------------

func TestAnalyticsHandler_RunReport_Unauthenticated(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/analytics/reports/abc123", nil)
	// No user in context

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	assert.Panics(t, func() { handler.RunReport(c) })
}

func TestAnalyticsHandler_RunReport_EmptyReportID(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/analytics/reports/", nil)
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})
	// reportId param is empty string
	c.Params = gin.Params{{Key: "reportId", Value: ""}}

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	handler.RunReport(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAnalyticsHandler_RunReportExecution_Unauthenticated(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/analytics/report-execution", bytes.NewReader([]byte(`{"reportId":"abc123"}`)))
	c.Request.Header.Set("Content-Type", "application/json")

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	assert.Panics(t, func() { handler.RunReportExecution(c) })
}

func TestAnalyticsHandler_RunReportExecution_MissingInputs(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/analytics/report-execution", bytes.NewReader([]byte(`{}`)))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	handler.RunReportExecution(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// ---------------------------------------------------------------------------
// AnalyticsHandler.DescribeReport — input validation
// ---------------------------------------------------------------------------

func TestAnalyticsHandler_DescribeReport_Unauthenticated(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/analytics/reports/abc/describe", nil)

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	assert.Panics(t, func() { handler.DescribeReport(c) })
}

func TestAnalyticsHandler_DescribeReport_EmptyReportID(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/analytics/reports//describe", nil)
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})
	c.Params = gin.Params{{Key: "reportId", Value: ""}}

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	handler.DescribeReport(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// ---------------------------------------------------------------------------
// AnalyticsHandler.ListReports — unauthenticated
// ---------------------------------------------------------------------------

func TestAnalyticsHandler_ListReports_Unauthenticated(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/analytics/reports", nil)

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	assert.Panics(t, func() { handler.ListReports(c) })
}

// ---------------------------------------------------------------------------
// AnalyticsHandler.CreateReport — validation
// ---------------------------------------------------------------------------

func TestAnalyticsHandler_ExecuteReportDefinition_Unauthenticated(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/analytics/reports/execute", nil)

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	assert.Panics(t, func() { handler.ExecuteReportDefinition(c) })
}

func TestAnalyticsHandler_ExecuteReportDefinition_MissingName(t *testing.T) {
	body := map[string]interface{}{
		"reportMetadata": map[string]interface{}{
			"reportType": map[string]interface{}{"type": "AccountList"},
		},
	}
	bodyBytes, _ := json.Marshal(body)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/analytics/reports/execute", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	handler.ExecuteReportDefinition(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAnalyticsHandler_ExecuteReportDefinition_MissingReportType(t *testing.T) {
	body := map[string]interface{}{
		"reportMetadata": map[string]interface{}{
			"name": "Draft Report",
		},
	}
	bodyBytes, _ := json.Marshal(body)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/analytics/reports/execute", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	handler.ExecuteReportDefinition(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAnalyticsHandler_ExecuteReportDefinition_Success(t *testing.T) {
	body := map[string]interface{}{
		"reportMetadata": map[string]interface{}{
			"id":            "rpt-draft-1",
			"name":          "Draft Report",
			"reportFormat":  "TABULAR",
			"reportType":    map[string]interface{}{"type": "AccountList", "label": "Accounts"},
			"detailColumns": []string{"Name"},
		},
	}
	bodyBytes, _ := json.Marshal(body)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/analytics/reports/execute", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})

	reportSvc := analyticsfeature.NewReportService(&executeReportDefinitionQueryService{}, nil, nil, nil)
	handler := rest.NewAnalyticsHandler(reportSvc, nil, nil)
	handler.ExecuteReportDefinition(c)

	assert.Equal(t, http.StatusOK, w.Code)

	var resp map[string]interface{}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
	meta := resp["reportMetadata"].(map[string]interface{})
	assert.Equal(t, "Draft Report", meta["name"])
	assert.Equal(t, "rpt-draft-1", meta["id"])
	assert.Contains(t, resp, "factMap")
}

func TestAnalyticsHandler_ExecuteReportDefinition_AppliesScopeAndStandardDateFilter(t *testing.T) {
	body := map[string]interface{}{
		"reportMetadata": map[string]interface{}{
			"name":         "Scoped Draft Report",
			"reportFormat": "TABULAR",
			"reportType":   map[string]interface{}{"type": "AccountList", "label": "Accounts"},
			"scope":        "mine",
			"detailColumns": []string{
				"Name",
			},
			"standardDateFilter": map[string]interface{}{
				"column":        "CreatedDate",
				"durationValue": "TODAY",
				"startDate":     "2026-04-09",
				"endDate":       "2026-04-09",
			},
		},
	}
	bodyBytes, _ := json.Marshal(body)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/analytics/reports/execute", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user-123",
		ProfileId: constants.ProfileSystemAdmin,
	})

	querySvc := &executeReportDefinitionQueryService{}
	reportSvc := analyticsfeature.NewReportService(querySvc, nil, nil, nil)
	handler := rest.NewAnalyticsHandler(reportSvc, nil, nil)
	handler.ExecuteReportDefinition(c)

	assert.Equal(t, http.StatusOK, w.Code)
	assert.Equal(t, "Account", querySvc.lastQuery.ObjectAPIName)
	assert.True(t, querySvc.hasCriterion("OwnerId", "=", "user-123"))
	assert.True(t, querySvc.hasCriterion("CreatedDate", ">=", "2026-04-09 00:00:00"))
	assert.True(t, querySvc.hasCriterion("CreatedDate", "<=", "2026-04-09 23:59:59"))
}

func TestAnalyticsHandler_CreateReport_Unauthenticated(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/analytics/reports", nil)

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	assert.Panics(t, func() { handler.CreateReport(c) })
}

func TestAnalyticsHandler_CreateReport_InvalidJSON(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/analytics/reports", bytes.NewReader([]byte(`{invalid`)))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	handler.CreateReport(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAnalyticsHandler_CreateReport_MissingName(t *testing.T) {
	body := map[string]interface{}{
		"reportMetadata": map[string]interface{}{
			"name": "",
		},
	}
	bodyBytes, _ := json.Marshal(body)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/analytics/reports", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	handler.CreateReport(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAnalyticsHandler_CreateReport_MissingReportType(t *testing.T) {
	body := map[string]interface{}{
		"reportMetadata": map[string]interface{}{
			"name": "My Report",
			// no reportType → sourceObject will be empty
		},
	}
	bodyBytes, _ := json.Marshal(body)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/analytics/reports", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	handler.CreateReport(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)

	var resp []map[string]interface{}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
	assert.NotEmpty(t, resp)
}

// ---------------------------------------------------------------------------
// AnalyticsHandler.UpdateReport — validation
// ---------------------------------------------------------------------------

func TestAnalyticsHandler_UpdateReport_Unauthenticated(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("PATCH", "/analytics/reports/rpt1", nil)

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	assert.Panics(t, func() { handler.UpdateReport(c) })
}

func TestAnalyticsHandler_UpdateReport_EmptyReportID(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("PATCH", "/analytics/reports/", nil)
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})
	c.Params = gin.Params{{Key: "reportId", Value: ""}}

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	handler.UpdateReport(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

func TestAnalyticsHandler_UpdateReport_EmptyUpdate(t *testing.T) {
	body := map[string]interface{}{
		"reportMetadata": map[string]interface{}{},
	}
	bodyBytes, _ := json.Marshal(body)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("PATCH", "/analytics/reports/rpt1", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})
	c.Params = gin.Params{{Key: "reportId", Value: "rpt1"}}

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	handler.UpdateReport(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}

// ---------------------------------------------------------------------------
// AnalyticsHandler.DeleteReport — validation
// ---------------------------------------------------------------------------

func TestAnalyticsHandler_DeleteReport_Unauthenticated(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("DELETE", "/analytics/reports/rpt1", nil)

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	assert.Panics(t, func() { handler.DeleteReport(c) })
}

func TestAnalyticsHandler_DeleteReport_EmptyReportID(t *testing.T) {
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("DELETE", "/analytics/reports/", nil)
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})
	c.Params = gin.Params{{Key: "reportId", Value: ""}}

	handler := rest.NewAnalyticsHandler(nil, nil, nil)
	handler.DeleteReport(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)
}
