package incident

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

	"github.com/gin-gonic/gin"
	devopsapi "github.com/tidbcloud/devops/api"
	incidentapi "github.com/tidbcloud/devops/api/incident"
	"github.com/tidbcloud/devops/entity"
	"github.com/tidbcloud/devops/pkg/infra/config"
	serviceincident "github.com/tidbcloud/devops/pkg/service/incident"
	"go.uber.org/mock/gomock"
)

func TestListIncidents(t *testing.T) {
	gin.SetMode(gin.TestMode)
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := serviceincident.NewMockIncidentService(ctrl)
	controller := &IncidentController{incidentSvc: mockSvc}
	warRoomID := uint64(0)
	status := entity.IncidentStatusRunning
	tier := entity.IncidentTierT0
	mockSvc.EXPECT().
		ListIncidents(gomock.Any(), serviceincident.ListIncidentsParam{
			Page:              2,
			PerPage:           10,
			WarRoomID:         &warRoomID,
			StatusIs:          &status,
			TierIs:            &tier,
			ComponentsContain: []string{"TiDB", "TiKV"},
			BizRegionsContain: []string{"APAC", "EMEA"},
		}).
		Return([]*entity.Incident{{Id: 3, WarRoomID: &warRoomID}}, int64(12), nil)

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/incidents?page=2&per_page=10&war_room_id=0&status=running&tier=t0&components=TiDB&components=TiKV&biz_regions=APAC&biz_regions=EMEA", nil)

	controller.ListIncidents(ctx)

	if recorder.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d body=%s", recorder.Code, recorder.Body.String())
	}
	var resp incidentapi.ListIncidentsResp
	if err := json.Unmarshal(recorder.Body.Bytes(), &resp); err != nil {
		t.Fatalf("unmarshal response failed: %v", err)
	}
	if resp.ErrCode != devopsapi.ErrCodeOK || resp.Total != 12 || len(resp.Incidents) != 1 || resp.Incidents[0].Id != 3 {
		t.Fatalf("unexpected response: %#v", resp)
	}
}

func TestListIncidentsUsesDefaultPagination(t *testing.T) {
	gin.SetMode(gin.TestMode)
	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockSvc := serviceincident.NewMockIncidentService(ctrl)
	controller := &IncidentController{incidentSvc: mockSvc}
	mockSvc.EXPECT().
		ListIncidents(gomock.Any(), serviceincident.ListIncidentsParam{Page: 1, PerPage: 50}).
		Return(nil, int64(0), nil)

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodGet, "/incidents", nil)

	controller.ListIncidents(ctx)

	if recorder.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d body=%s", recorder.Code, recorder.Body.String())
	}
}

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

	for _, target := range []string{"/incidents?page=0", "/incidents?per_page=101"} {
		t.Run(target, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			controller := &IncidentController{incidentSvc: serviceincident.NewMockIncidentService(ctrl)}
			recorder := httptest.NewRecorder()
			ctx, _ := gin.CreateTestContext(recorder)
			ctx.Request = httptest.NewRequest(http.MethodGet, target, nil)

			controller.ListIncidents(ctx)

			if recorder.Code != http.StatusBadRequest {
				t.Fatalf("expected 400, got %d body=%s", recorder.Code, recorder.Body.String())
			}
			var resp devopsapi.BaseResp
			if err := json.Unmarshal(recorder.Body.Bytes(), &resp); err != nil {
				t.Fatalf("unmarshal response failed: %v", err)
			}
			if resp.ErrCode != devopsapi.ErrCodeInvalidArgument {
				t.Fatalf("expected invalid argument, got %v", resp.ErrCode)
			}
		})
	}
}

func TestNormalizeIncidentComponentDefinitions(t *testing.T) {
	got := normalizeIncidentComponentDefinitions([]config.IncidentComponentConfig{
		{Key: "tikv", Name: "TiKV", Enabled: ptr(true), Order: 20},
		{},
		{Key: "tidb", Name: "TiDB", Enabled: ptr(true), Order: 10},
		{Key: "tikv", Name: "Duplicated", Enabled: ptr(true), Order: 30},
		{Key: "pd", Enabled: ptr(true), Order: 15},
	})

	want := []incidentapi.IncidentComponentDefinition{
		{Key: "tidb", Name: "TiDB", Enabled: true, Order: 10},
		{Key: "pd", Name: "pd", Enabled: true, Order: 15},
		{Key: "tikv", Name: "TiKV", Enabled: true, Order: 20},
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("expected %v, got %v", want, got)
	}
}

func TestIncidentComponentDefinitionsUseConfigWhenPresent(t *testing.T) {
	c := &IncidentController{
		conf: config.IncidentServiceConfig{
			ComponentDefinitions: []config.IncidentComponentConfig{
				{Key: "tidb", Name: "TiDB", Enabled: ptr(true), Order: 20},
				{Key: "tikv", Name: "TiKV", Enabled: ptr(false), Order: 10},
			},
		},
	}

	got := c.incidentComponentDefinitions()
	want := []incidentapi.IncidentComponentDefinition{
		{Key: "tikv", Name: "TiKV", Enabled: false, Order: 10},
		{Key: "tidb", Name: "TiDB", Enabled: true, Order: 20},
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("expected %v, got %v", want, got)
	}
}

func TestIncidentComponentOptionsUseEnabledDefinitionNames(t *testing.T) {
	c := &IncidentController{
		conf: config.IncidentServiceConfig{
			ComponentDefinitions: []config.IncidentComponentConfig{
				{Key: "tidb", Name: "TiDB", Enabled: ptr(true), Order: 20},
				{Key: "tikv", Name: "TiKV", Enabled: ptr(false), Order: 10},
				{Key: "pd", Enabled: ptr(true), Order: 30},
			},
		},
	}

	got := c.incidentComponentOptions()
	want := []string{"TiDB", "pd"}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("expected %v, got %v", want, got)
	}
}

func TestValidateIncidentComponents(t *testing.T) {
	c := &IncidentController{
		conf: config.IncidentServiceConfig{
			ComponentDefinitions: []config.IncidentComponentConfig{
				{Key: "tidb", Name: "TiDB", Enabled: ptr(true), Order: 10},
				{Key: "tikv", Name: "TiKV", Enabled: ptr(true), Order: 20},
				{Key: "pd", Name: "PD", Enabled: ptr(false), Order: 30},
			},
		},
	}

	if err := c.validateIncidentComponents("TiDB TiKV"); err != nil {
		t.Fatalf("expected valid components, got error %v", err)
	}

	if err := c.validateIncidentComponents("PD"); err == nil {
		t.Fatalf("expected disabled component to be rejected")
	}

	if err := c.validateIncidentComponents(""); err != nil {
		t.Fatalf("expected empty components to be allowed, got error %v", err)
	}

	err := c.validateIncidentComponents("TiDB unknown unknown")
	if err == nil {
		t.Fatalf("expected invalid components error")
	}
	if got, want := err.Error(), "invalid incident components: unknown"; got != want {
		t.Fatalf("expected %q, got %q", want, got)
	}
}
