package healthcheck

import (
	"errors"
	"testing"
	"time"

	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/stretchr/testify/require"
	"gorm.io/gorm"

	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/db/model"
)

type fakeHealthCheckReportQueryRepository struct {
	results       []db.HealthCheckResult
	total         int64
	listErr       error
	report        *db.HealthCheckResult
	getErr        error
	orgID         string
	clusterID     string
	reportID      string
	listOptions   model.HealthCheckResultListOptions
	updatedID     string
	updatedStatus string
	updatedError  string
	updateErr     error
}

func (f *fakeHealthCheckReportQueryRepository) ListHealthCheckResultsByScope(
	orgID, clusterID string,
	options model.HealthCheckResultListOptions,
) ([]db.HealthCheckResult, int64, error) {
	f.orgID = orgID
	f.clusterID = clusterID
	f.listOptions = options
	return f.results, f.total, f.listErr
}

func (f *fakeHealthCheckReportQueryRepository) GetHealthCheckResultByScope(
	orgID, clusterID, reportID string,
) (*db.HealthCheckResult, error) {
	f.orgID = orgID
	f.clusterID = clusterID
	f.reportID = reportID
	return f.report, f.getErr
}

func (f *fakeHealthCheckReportQueryRepository) UpdateHealthCheckResultStatus(
	uuid, status, errorMsg string,
) error {
	f.updatedID = uuid
	f.updatedStatus = status
	f.updatedError = errorMsg
	if f.updateErr != nil {
		return f.updateErr
	}
	for i := range f.results {
		if f.results[i].UUID == uuid {
			f.results[i].Status = status
			f.results[i].ErrorMsg = errorMsg
		}
	}
	if f.report != nil && f.report.UUID == uuid {
		f.report.Status = status
		f.report.ErrorMsg = errorMsg
	}
	return nil
}

type failingHealthCheckReportS3Client struct{}

func (failingHealthCheckReportS3Client) GetObject(*s3.GetObjectInput) (*s3.GetObjectOutput, error) {
	return nil, errors.New("storage unavailable")
}

func TestHealthCheckReportQueryListDoesNotReadS3ForTerminalStatus(t *testing.T) {
	createdAt := time.Date(2026, 7, 19, 3, 0, 0, 0, time.UTC)
	from := createdAt.Add(-24 * time.Hour)
	to := createdAt.Add(24 * time.Hour)
	repository := &fakeHealthCheckReportQueryRepository{
		results: []db.HealthCheckResult{{
			UUID:         "report-a",
			StrategyUUID: "strategy-a",
			DbaasOrgID:   "123",
			ClusterID:    "cluster-a",
			Status:       string(db.StatusSuccess),
			ItemsToRun:   `[{"name":"slowlog_top_digest","checkType":"sql","enabled":true}]`,
			Model:        gorm.Model{CreatedAt: createdAt, UpdatedAt: createdAt},
		}},
		total: 1,
	}
	service := newHealthCheckReportQueryService(repository, failingHealthCheckReportS3Client{})

	page, err := service.ListReports("123", "cluster-a", HealthCheckReportListOptions{
		Page: 2, PageSize: 25, Statuses: []string{"success"}, CreatedFrom: &from, CreatedTo: &to,
	})

	require.NoError(t, err)
	require.Equal(t, "123", repository.orgID)
	require.Equal(t, "cluster-a", repository.clusterID)
	require.Equal(t, 2, repository.listOptions.Page)
	require.Equal(t, []string{"success"}, repository.listOptions.Statuses)
	require.Len(t, page.Items, 1)
	require.Equal(t, "report-a", page.Items[0].ReportID)
	require.Equal(t, "slowlog_top_digest", page.Items[0].CheckItems[0].Name)
}

func TestHealthCheckReportQueryListReconcilesAndRepairsPartialStatus(t *testing.T) {
	repository := &fakeHealthCheckReportQueryRepository{
		results: []db.HealthCheckResult{{
			UUID:       "report-a",
			DbaasOrgID: "123",
			ClusterID:  "cluster-a",
			Status:     string(db.StatusPartial),
			ItemsToRun: `[
				{"name":"metrics_healthcheck","checkType":"metrics","enabled":true},
				{"name":"config_check","checkType":"config","enabled":true}
			]`,
		}},
		total: 1,
	}
	service := newHealthCheckReportQueryService(repository, &fakeS3Client{objects: map[string]string{
		"healthcheck-report/cluster-a_report-a_metrics_healthcheck.json": `{"status":"success"}`,
		"healthcheck-report/cluster-a_report-a_config_check.json":        `{"status":"success"}`,
	}})

	page, err := service.ListReports("123", "cluster-a", HealthCheckReportListOptions{Page: 1, PageSize: 20})

	require.NoError(t, err)
	require.Len(t, page.Items, 1)
	require.Equal(t, string(db.StatusSuccess), page.Items[0].Status)
	require.Equal(t, []string{"success", "success"}, []string{
		page.Items[0].CheckItems[0].Status,
		page.Items[0].CheckItems[1].Status,
	})
	require.Equal(t, "report-a", repository.updatedID)
	require.Equal(t, string(db.StatusSuccess), repository.updatedStatus)
}

func TestHealthCheckReportQueryDetailIsScopedAndFiltersItems(t *testing.T) {
	createdAt := time.Date(2026, 7, 19, 3, 0, 0, 0, time.UTC)
	report := &db.HealthCheckResult{
		UUID:         "report-a",
		StrategyUUID: "strategy-a",
		DbaasOrgID:   "123",
		ClusterID:    "cluster-a",
		Status:       string(db.StatusSuccess),
		ItemsToRun: `[
			{"name":"slowlog_top_digest","checkType":"sql","enabled":true},
			{"name":"metrics_healthcheck","checkType":"metrics","enabled":true}
		]`,
		Model: gorm.Model{CreatedAt: createdAt, UpdatedAt: createdAt},
	}
	repository := &fakeHealthCheckReportQueryRepository{report: report}
	service := newHealthCheckReportQueryService(repository, &fakeS3Client{objects: map[string]string{
		"healthcheck-report/cluster-a_report-a_slowlog_top_digest.json":  `{"status":"success","results":[{"digest":"digest-a"}]}`,
		"healthcheck-report/cluster-a_report-a_metrics_healthcheck.json": `{"status":"success"}`,
	}})

	detail, err := service.GetReport("123", "cluster-a", "report-a", []string{"slowlog_top_digest"})

	require.NoError(t, err)
	require.Equal(t, "123", repository.orgID)
	require.Equal(t, "cluster-a", repository.clusterID)
	require.Equal(t, "report-a", repository.reportID)
	require.Len(t, detail.Items, 1)
	require.Equal(t, string(db.StatusSuccess), detail.Status)
	require.Equal(t, "slowlog_top_digest", detail.Items[0].Name)
	require.JSONEq(t, `{"status":"success","results":[{"digest":"digest-a"}]}`, string(detail.Items[0].Content))
}

func TestHealthCheckReportQueryDetailReconcilesWholeReportWhenSelectingItems(t *testing.T) {
	repository := &fakeHealthCheckReportQueryRepository{report: &db.HealthCheckResult{
		UUID: "report-a", DbaasOrgID: "123", ClusterID: "cluster-a", Status: string(db.StatusPartial),
		ItemsToRun: `[
			{"name":"slowlog_top_digest","checkType":"sql"},
			{"name":"metrics_healthcheck","checkType":"metrics"}
		]`,
	}}
	service := newHealthCheckReportQueryService(repository, &fakeS3Client{objects: map[string]string{
		"healthcheck-report/cluster-a_report-a_slowlog_top_digest.json":  `{"status":"success","results":[]}`,
		"healthcheck-report/cluster-a_report-a_metrics_healthcheck.json": `{"status":"success"}`,
	}})

	detail, err := service.GetReport("123", "cluster-a", "report-a", []string{"slowlog_top_digest"})

	require.NoError(t, err)
	require.Equal(t, string(db.StatusSuccess), detail.Status)
	require.Len(t, detail.Items, 1)
	require.Equal(t, "slowlog_top_digest", detail.Items[0].Name)
	require.Equal(t, string(db.StatusSuccess), repository.updatedStatus)
}

func TestHealthCheckReportQueryRejectsUnknownItemBeforeReadingS3(t *testing.T) {
	repository := &fakeHealthCheckReportQueryRepository{report: &db.HealthCheckResult{
		UUID: "report-a", DbaasOrgID: "123", ClusterID: "cluster-a",
		ItemsToRun: `[{"name":"slowlog_top_digest","checkType":"sql"}]`,
	}}
	service := newHealthCheckReportQueryService(repository, failingHealthCheckReportS3Client{})

	_, err := service.GetReport("123", "cluster-a", "report-a", []string{"unknown"})

	require.ErrorIs(t, err, ErrHealthCheckReportItemNotFound)
}

func TestHealthCheckReportQueryReportsStorageFailures(t *testing.T) {
	repository := &fakeHealthCheckReportQueryRepository{report: &db.HealthCheckResult{
		UUID: "report-a", DbaasOrgID: "123", ClusterID: "cluster-a",
		ItemsToRun: `[{"name":"slowlog_top_digest","checkType":"sql"}]`,
	}}
	service := newHealthCheckReportQueryService(repository, failingHealthCheckReportS3Client{})

	_, err := service.GetReport("123", "cluster-a", "report-a", nil)

	require.ErrorIs(t, err, ErrHealthCheckReportStorageUnavailable)
}
