package internal_api

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

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"

	error2 "github.com/pingcap-inc/clinic/pkg/api/error"
	"github.com/pingcap-inc/clinic/pkg/api/healthcheck"
	"github.com/pingcap-inc/clinic/pkg/auth/session"
)

type fakeHealthCheckReportQuery struct {
	page        *healthcheck.HealthCheckReportPage
	report      *healthcheck.HealthCheckReport
	err         error
	orgID       string
	clusterID   string
	reportID    string
	itemNames   []string
	listOptions healthcheck.HealthCheckReportListOptions
}

func (f *fakeHealthCheckReportQuery) ListReports(
	orgID, clusterID string,
	options healthcheck.HealthCheckReportListOptions,
) (*healthcheck.HealthCheckReportPage, error) {
	f.orgID = orgID
	f.clusterID = clusterID
	f.listOptions = options
	return f.page, f.err
}

func (f *fakeHealthCheckReportQuery) GetReport(
	orgID, clusterID, reportID string,
	itemNames []string,
) (*healthcheck.HealthCheckReport, error) {
	f.orgID = orgID
	f.clusterID = clusterID
	f.reportID = reportID
	f.itemNames = itemNames
	return f.report, f.err
}

func TestDataProxyHealthCheckReportListValidatesAndDelegates(t *testing.T) {
	query := &fakeHealthCheckReportQuery{page: &healthcheck.HealthCheckReportPage{
		Items: []healthcheck.HealthCheckReportSummary{{ReportID: "report-a"}},
		Page:  2, PageSize: 25, Total: 30,
	}}
	router := healthCheckReportTestRouter(query)
	response := performHealthCheckReportRequest(router,
		"/clinic/api/v1/internal/data-proxy/v1/orgs/123/clusters/cluster-a/health-check/reports?page=2&pageSize=25&status=success&status=partial_failed&createdFrom=2026-07-01T00%3A00%3A00Z&createdTo=2026-08-01T00%3A00%3A00Z")

	require.Equal(t, http.StatusOK, response.Code, response.Body.String())
	require.Equal(t, "456", query.orgID)
	require.Equal(t, "cluster-a", query.clusterID)
	require.Equal(t, 2, query.listOptions.Page)
	require.Equal(t, []string{"partial_failed", "success"}, query.listOptions.Statuses)
	require.Equal(t, time.Date(2026, 7, 1, 0, 0, 0, 0, time.UTC), *query.listOptions.CreatedFrom)
	require.JSONEq(t, `{"items":[{"reportId":"report-a","strategyId":"","orgId":"123","portalOrgId":"456","clusterId":"","status":"","createdAt":"0001-01-01T00:00:00Z","updatedAt":"0001-01-01T00:00:00Z","checkItems":null}],"page":2,"pageSize":25,"total":30}`, response.Body.String())
}

func TestDataProxyHealthCheckReportDetailForwardsSelectedItems(t *testing.T) {
	query := &fakeHealthCheckReportQuery{report: &healthcheck.HealthCheckReport{
		ReportID: "report-a", OrgID: "123", ClusterID: "cluster-a", Status: "success", Items: []healthcheck.HealthCheckReportItem{},
	}}
	response := performHealthCheckReportRequest(healthCheckReportTestRouter(query),
		"/clinic/api/v1/internal/data-proxy/v1/orgs/123/clusters/cluster-a/health-check/reports/report-a?itemName=slowlog_top_digest&itemName=tablefullscan_slow_query")

	require.Equal(t, http.StatusOK, response.Code, response.Body.String())
	require.Equal(t, "report-a", query.reportID)
	require.Equal(t, "456", query.orgID)
	require.Equal(t, []string{"slowlog_top_digest", "tablefullscan_slow_query"}, query.itemNames)
	var body healthcheck.HealthCheckReport
	require.NoError(t, json.Unmarshal(response.Body.Bytes(), &body))
	require.Equal(t, "report-a", body.ReportID)
	require.Equal(t, "123", body.OrgID)
	require.Equal(t, "456", body.PortalOrgID)
}

func TestDataProxyHealthCheckReportRoutesRejectUnknownOrganization(t *testing.T) {
	query := &fakeHealthCheckReportQuery{}
	response := performHealthCheckReportRequest(
		healthCheckReportTestRouterWithResolver(query, func(string) (uint, error) {
			return 0, gorm.ErrRecordNotFound
		}),
		"/clinic/api/v1/internal/data-proxy/v1/orgs/123/clusters/cluster-a/health-check/reports",
	)

	require.Equal(t, http.StatusNotFound, response.Code, response.Body.String())
	require.Empty(t, query.orgID)
}

func TestDataProxyHealthCheckReportRoutesHandleOrganizationResolutionFailure(t *testing.T) {
	query := &fakeHealthCheckReportQuery{}
	response := performHealthCheckReportRequest(
		healthCheckReportTestRouterWithResolver(query, func(string) (uint, error) {
			return 0, errors.New("database unavailable")
		}),
		"/clinic/api/v1/internal/data-proxy/v1/orgs/123/clusters/cluster-a/health-check/reports",
	)

	require.Equal(t, http.StatusInternalServerError, response.Code, response.Body.String())
	require.Empty(t, query.orgID)
}

func TestDataProxyHealthCheckReportRoutesRejectInvalidInput(t *testing.T) {
	paths := []string{
		"/clinic/api/v1/internal/data-proxy/v1/orgs/nope/clusters/cluster-a/health-check/reports",
		"/clinic/api/v1/internal/data-proxy/v1/orgs/123/clusters/cluster-a/health-check/reports?pageSize=101",
		"/clinic/api/v1/internal/data-proxy/v1/orgs/123/clusters/cluster-a/health-check/reports?status=deleted",
		"/clinic/api/v1/internal/data-proxy/v1/orgs/123/clusters/cluster-a/health-check/reports?unexpected=true",
		"/clinic/api/v1/internal/data-proxy/v1/orgs/123/clusters/cluster-a/health-check/reports/report-a?itemName=",
	}
	for _, path := range paths {
		response := performHealthCheckReportRequest(healthCheckReportTestRouter(&fakeHealthCheckReportQuery{}), path)
		require.Equal(t, http.StatusBadRequest, response.Code, path+": "+response.Body.String())
	}
}

func TestDataProxyHealthCheckReportDetailHidesCrossScopeReports(t *testing.T) {
	query := &fakeHealthCheckReportQuery{err: healthcheck.ErrHealthCheckReportNotFound}
	response := performHealthCheckReportRequest(healthCheckReportTestRouter(query),
		"/clinic/api/v1/internal/data-proxy/v1/orgs/123/clusters/wrong-cluster/health-check/reports/report-a")

	require.Equal(t, http.StatusNotFound, response.Code, response.Body.String())
}

func TestDataProxyHealthCheckReportDetailMapsStorageFailure(t *testing.T) {
	query := &fakeHealthCheckReportQuery{err: healthcheck.ErrHealthCheckReportStorageUnavailable}
	response := performHealthCheckReportRequest(healthCheckReportTestRouter(query),
		"/clinic/api/v1/internal/data-proxy/v1/orgs/123/clusters/cluster-a/health-check/reports/report-a")

	require.Equal(t, http.StatusServiceUnavailable, response.Code, response.Body.String())
}

func healthCheckReportTestRouter(query healthCheckReportQuery) *gin.Engine {
	return healthCheckReportTestRouterWithResolver(query, func(string) (uint, error) {
		return 456, nil
	})
}

func healthCheckReportTestRouterWithResolver(
	query healthCheckReportQuery,
	resolver healthCheckReportOrganizationResolver,
) *gin.Engine {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(error2.ErrorHandlerFn)
	router.Use(func(c *gin.Context) {
		c.Set(session.CtxUserKey, &session.CtxUser{UserID: 3, Email: "clinic@pingcap.com", Internal: true})
		c.Next()
	})
	registerDataProxyHealthCheckReportRoutes(
		router.Group("/clinic/api/v1/internal"),
		newDataProxyHealthCheckReportService(query, resolver),
	)
	return router
}

func performHealthCheckReportRequest(router http.Handler, target string) *httptest.ResponseRecorder {
	request := httptest.NewRequest(http.MethodGet, target, nil)
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)
	return response
}
