package incident

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

	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"

	"github.com/gin-gonic/gin"
)

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

	mockSvc := serviceincident.NewMockIncidentService(ctrl)
	controller := &IncidentController{
		incidentSvc: mockSvc,
		conf:        config.IncidentServiceConfig{},
	}

	reqBody := incidentapi.UpdateIncidentReq{
		FeishuUrl:                    strPtr("https://tidb.feishu.cn/docx/abc123"),
		ExtractPostMortemToDoActions: true,
	}
	body, err := json.Marshal(reqBody)
	if err != nil {
		t.Fatalf("marshal request failed: %v", err)
	}

	mockSvc.EXPECT().
		GetIncident(gomock.Any(), uint64(1)).
		Return(&entity.Incident{Id: 1, Name: "incident"}, nil)
	mockSvc.EXPECT().
		UpdateIncident(gomock.Any(), uint64(1), gomock.Any()).
		DoAndReturn(func(ctx context.Context, id uint64, updated *entity.Incident) error {
			if updated.FeishuUrl != "https://tidb.feishu.cn/docx/abc123" {
				t.Fatalf("unexpected feishu url: %s", updated.FeishuUrl)
			}
			if updated.PostMortemToDoActionImportStatus != entity.PostMortemToDoActionImportStatusImporting {
				t.Fatalf("unexpected import status: %s", updated.PostMortemToDoActionImportStatus)
			}
			return nil
		})
	importStarted := make(chan struct{})
	mockSvc.EXPECT().
		CreatePostMortemToDoActionsFromFeishuDoc(gomock.Any(), uint64(1), "https://tidb.feishu.cn/docx/abc123").
		DoAndReturn(func(ctx context.Context, id uint64, feishuURL string) ([]*entity.IncidentPostMortemToDoAction, error) {
			close(importStarted)
			return nil, nil
		})
	importCompleted := make(chan struct{})
	mockSvc.EXPECT().
		UpdatePostMortemToDoActionImportStatus(gomock.Any(), uint64(1), entity.PostMortemToDoActionImportStatusSucceeded).
		DoAndReturn(func(ctx context.Context, id uint64, status string) error {
			close(importCompleted)
			return nil
		})

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodPut, "/incidents/1", bytes.NewReader(body))
	ctx.Params = gin.Params{{Key: "incident_id", Value: "1"}}

	controller.UpdateIncident(ctx)

	if recorder.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d body=%s", recorder.Code, recorder.Body.String())
	}
	if !bytes.Contains(recorder.Body.Bytes(), []byte(`"postmortem_todo_action_import_started":true`)) {
		t.Fatalf("expected import started response, got body=%s", recorder.Body.String())
	}
	select {
	case <-importStarted:
	case <-time.After(time.Second):
		t.Fatalf("expected async import to be started")
	}
	select {
	case <-importCompleted:
	case <-time.After(time.Second):
		t.Fatalf("expected async import status to be updated")
	}
}

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

	mockSvc := serviceincident.NewMockIncidentService(ctrl)
	controller := &IncidentController{
		incidentSvc: mockSvc,
		conf:        config.IncidentServiceConfig{},
	}

	reqBody := `{"extract_postmortem_todo_actions":true}`
	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodPut, "/incidents/1", bytes.NewBufferString(reqBody))
	ctx.Params = gin.Params{{Key: "incident_id", Value: "1"}}

	controller.UpdateIncident(ctx)

	if recorder.Code == http.StatusOK {
		t.Fatalf("expected non-200 response")
	}
}

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

	mockSvc := serviceincident.NewMockIncidentService(ctrl)
	controller := &IncidentController{
		incidentSvc: mockSvc,
		conf:        config.IncidentServiceConfig{},
	}

	mockSvc.EXPECT().
		GetIncident(gomock.Any(), uint64(1)).
		Return(&entity.Incident{Id: 1, FeishuUrl: "https://tidb.feishu.cn/docx/abc123"}, nil)
	mockSvc.EXPECT().
		ListIncidentPostMortemToDoActions(gomock.Any(), uint64(1), nil).
		Return(nil, nil)
	mockSvc.EXPECT().
		UpdatePostMortemToDoActionImportStatus(gomock.Any(), uint64(1), entity.PostMortemToDoActionImportStatusImporting).
		Return(nil)

	importStarted := make(chan struct{})
	mockSvc.EXPECT().
		CreatePostMortemToDoActionsFromFeishuDoc(gomock.Any(), uint64(1), "https://tidb.feishu.cn/docx/abc123").
		DoAndReturn(func(ctx context.Context, id uint64, feishuURL string) ([]*entity.IncidentPostMortemToDoAction, error) {
			close(importStarted)
			return nil, nil
		})
	importCompleted := make(chan struct{})
	mockSvc.EXPECT().
		UpdatePostMortemToDoActionImportStatus(gomock.Any(), uint64(1), entity.PostMortemToDoActionImportStatusSucceeded).
		DoAndReturn(func(ctx context.Context, id uint64, status string) error {
			close(importCompleted)
			return nil
		})

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodPost, "/incidents/1/postmortem/todoactions/import", nil)
	ctx.Params = gin.Params{{Key: "incident_id", Value: "1"}}

	controller.ImportIncidentPostMortemToDoActions(ctx)

	if recorder.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d body=%s", recorder.Code, recorder.Body.String())
	}
	if !bytes.Contains(recorder.Body.Bytes(), []byte(`"postmortem_todo_action_import_status":"importing"`)) {
		t.Fatalf("expected importing response, got body=%s", recorder.Body.String())
	}
	select {
	case <-importStarted:
	case <-time.After(time.Second):
		t.Fatalf("expected async import to be started")
	}
	select {
	case <-importCompleted:
	case <-time.After(time.Second):
		t.Fatalf("expected async import status to be updated")
	}
}

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

	mockSvc := serviceincident.NewMockIncidentService(ctrl)
	controller := &IncidentController{
		incidentSvc: mockSvc,
		conf:        config.IncidentServiceConfig{},
	}

	actionID := uint64(10)
	mockSvc.EXPECT().
		GetIncident(gomock.Any(), uint64(1)).
		Return(&entity.Incident{Id: 1, FeishuUrl: "https://tidb.feishu.cn/docx/abc123"}, nil)
	mockSvc.EXPECT().
		ListIncidentPostMortemToDoActions(gomock.Any(), uint64(1), nil).
		Return([]*entity.IncidentPostMortemToDoAction{{Id: actionID}}, nil)

	recorder := httptest.NewRecorder()
	ctx, _ := gin.CreateTestContext(recorder)
	ctx.Request = httptest.NewRequest(http.MethodPost, "/incidents/1/postmortem/todoactions/import", nil)
	ctx.Params = gin.Params{{Key: "incident_id", Value: "1"}}

	controller.ImportIncidentPostMortemToDoActions(ctx)

	if recorder.Code == http.StatusOK {
		t.Fatalf("expected non-200 response")
	}
}

func strPtr(s string) *string {
	return &s
}
