package incident

import (
	"encoding/json"
	"testing"
	"time"

	coreuser "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/core-server/user/v1"
	"github.com/tidbcloud/devops/entity"
)

func TestCreateIncidentReqToIncidentEntityPreservesWarRoomID(t *testing.T) {
	warRoomID := uint64(0)

	incident := CreateIncidentReq{WarRoomID: &warRoomID}.ToIncidentEntity()

	if incident.WarRoomID == nil || *incident.WarRoomID != warRoomID {
		t.Fatalf("expected war room ID %d, got %#v", warRoomID, incident.WarRoomID)
	}
}

func TestUpdateIncidentReqWarRoomIDBehavior(t *testing.T) {
	originalWarRoomID := uint64(100)
	base := &entity.Incident{WarRoomID: &originalWarRoomID}

	tests := []struct {
		name          string
		body          string
		wantWarRoomID *uint64
	}{
		{
			name:          "omitted preserves the existing value",
			body:          `{}`,
			wantWarRoomID: &originalWarRoomID,
		},
		{
			name:          "null preserves the existing value",
			body:          `{"war_room_id":null}`,
			wantWarRoomID: &originalWarRoomID,
		},
		{
			name:          "number replaces the existing value",
			body:          `{"war_room_id":200}`,
			wantWarRoomID: uint64Pointer(200),
		},
		{
			name:          "zero is a valid value",
			body:          `{"war_room_id":0}`,
			wantWarRoomID: uint64Pointer(0),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var req UpdateIncidentReq
			if err := json.Unmarshal([]byte(tt.body), &req); err != nil {
				t.Fatalf("unmarshal update request failed: %v", err)
			}

			merged := req.ApplyToIncidentEntity(base)
			if tt.wantWarRoomID == nil {
				if merged.WarRoomID != nil {
					t.Fatalf("expected nil war room ID, got %d", *merged.WarRoomID)
				}
				return
			}
			if merged.WarRoomID == nil || *merged.WarRoomID != *tt.wantWarRoomID {
				t.Fatalf("expected war room ID %d, got %#v", *tt.wantWarRoomID, merged.WarRoomID)
			}
		})
	}
}

func uint64Pointer(value uint64) *uint64 {
	return &value
}

func TestUpdateIncidentReqApplyToIncidentEntityPreservesOmittedFields(t *testing.T) {
	start := time.Date(2026, 4, 28, 3, 4, 5, 0, time.UTC)
	base := &entity.Incident{
		Name:                 "original",
		Status:               "ongoing",
		Tier:                 "t1",
		Components:           "TiDB",
		BizRegions:           "apac",
		Oncallers:            []*coreuser.User{{Email: "oncaller@pingcap.com"}},
		Leaders:              []*coreuser.User{{Email: "leader@pingcap.com"}},
		FBOwners:             []*coreuser.User{{Email: "fb@pingcap.com"}},
		FeishuUrl:            "https://feishu.example/docx/abc",
		JiraIssues:           "https://tidb.atlassian.net/browse/ONCALL-1",
		Description:          "desc",
		IncidentStartTime:    &start,
		IncidentResolvedTime: &start,
	}

	name := "updated"
	req := UpdateIncidentReq{Name: &name}

	merged := req.ApplyToIncidentEntity(base)

	if merged.Name != "updated" {
		t.Fatalf("expected updated name, got %q", merged.Name)
	}
	if merged.JiraIssues != base.JiraIssues {
		t.Fatalf("expected jira issues to be preserved, got %q", merged.JiraIssues)
	}
	if merged.FeishuUrl != base.FeishuUrl {
		t.Fatalf("expected feishu url to be preserved, got %q", merged.FeishuUrl)
	}
	if merged.Components != base.Components {
		t.Fatalf("expected components to be preserved, got %q", merged.Components)
	}
	if merged.BizRegions != base.BizRegions {
		t.Fatalf("expected biz regions to be preserved, got %q", merged.BizRegions)
	}
	if merged.Description != base.Description {
		t.Fatalf("expected description to be preserved, got %q", merged.Description)
	}
	if merged.IncidentStartTime != base.IncidentStartTime {
		t.Fatalf("expected start time to be preserved")
	}
}

func TestUpdateIncidentReqApplyToIncidentEntityAllowsExplicitClear(t *testing.T) {
	base := &entity.Incident{
		FeishuUrl:  "https://feishu.example/docx/abc",
		JiraIssues: "https://tidb.atlassian.net/browse/ONCALL-1",
	}
	empty := ""

	req := UpdateIncidentReq{
		FeishuUrl:  &empty,
		JiraIssues: &empty,
	}

	merged := req.ApplyToIncidentEntity(base)

	if merged.FeishuUrl != "" {
		t.Fatalf("expected feishu url to be cleared, got %q", merged.FeishuUrl)
	}
	if merged.JiraIssues != "" {
		t.Fatalf("expected jira issues to be cleared, got %q", merged.JiraIssues)
	}
}

func TestParseIncidentJiraIssueLinks(t *testing.T) {
	links := ParseIncidentJiraIssueLinks("https://tidb.atlassian.net/browse/ONCALL-1 https://tidb.atlassian.net/browse/O11Y-2")

	if len(links) != 2 {
		t.Fatalf("expected 2 links, got %d", len(links))
	}
	if links[0].Key != "ONCALL-1" || links[0].URL != "https://tidb.atlassian.net/browse/ONCALL-1" {
		t.Fatalf("unexpected first link: %#v", links[0])
	}
	if links[1].Key != "O11Y-2" || links[1].URL != "https://tidb.atlassian.net/browse/O11Y-2" {
		t.Fatalf("unexpected second link: %#v", links[1])
	}
}

func TestSerializeIncidentJiraIssueLinks(t *testing.T) {
	serialized := SerializeIncidentJiraIssueLinks([]entity.IncidentJiraIssueLink{
		{Key: "ONCALL-1", URL: "https://tidb.atlassian.net/browse/ONCALL-1"},
		{Key: "O11Y-2", URL: "https://tidb.atlassian.net/browse/O11Y-2"},
	})

	expected := "https://tidb.atlassian.net/browse/ONCALL-1 https://tidb.atlassian.net/browse/O11Y-2"
	if serialized != expected {
		t.Fatalf("expected %q, got %q", expected, serialized)
	}
}

func TestUpdateIncidentReqApplyToIncidentEntityUsesStructuredJiraLinks(t *testing.T) {
	base := &entity.Incident{
		JiraIssues: "https://tidb.atlassian.net/browse/ONCALL-1",
	}
	links := []entity.IncidentJiraIssueLink{
		{Key: "O11Y-2", URL: "https://tidb.atlassian.net/browse/O11Y-2"},
	}

	req := UpdateIncidentReq{JiraIssueLinks: &links}
	merged := req.ApplyToIncidentEntity(base)

	if merged.JiraIssues != "https://tidb.atlassian.net/browse/O11Y-2" {
		t.Fatalf("expected serialized jira issues, got %q", merged.JiraIssues)
	}
	if len(merged.JiraIssueLinks) != 1 || merged.JiraIssueLinks[0].Key != "O11Y-2" {
		t.Fatalf("unexpected jira issue links: %#v", merged.JiraIssueLinks)
	}
}

func TestNormalizeIncidentTime(t *testing.T) {
	src := time.Date(2026, 4, 28, 11, 12, 59, 123456789, time.FixedZone("UTC+8", 8*60*60))

	got := NormalizeIncidentTime(&src)
	if got == nil {
		t.Fatalf("expected normalized time, got nil")
	}

	want := time.Date(2026, 4, 28, 3, 12, 59, 0, time.UTC)
	if !got.Equal(want) {
		t.Fatalf("expected %s, got %s", want.Format(time.RFC3339), got.Format(time.RFC3339))
	}
	if got.Location() != time.UTC {
		t.Fatalf("expected UTC location, got %s", got.Location())
	}
}

func TestCreateIncidentReqToIncidentEntityNormalizesTimes(t *testing.T) {
	start := time.Date(2026, 4, 28, 11, 12, 59, 0, time.FixedZone("UTC+8", 8*60*60))
	resolved := time.Date(2026, 4, 28, 11, 13, 1, 0, time.FixedZone("UTC+8", 8*60*60))

	entity := CreateIncidentReq{
		Name:                 "incident",
		Status:               "ongoing",
		Tier:                 "t1",
		IncidentStartTime:    &start,
		IncidentResolvedTime: &resolved,
	}.ToIncidentEntity()

	if entity.IncidentStartTime == nil || entity.IncidentResolvedTime == nil {
		t.Fatalf("expected normalized incident times")
	}
	if entity.IncidentStartTime.Format(time.RFC3339) != "2026-04-28T03:12:59Z" {
		t.Fatalf("unexpected normalized start time: %s", entity.IncidentStartTime.Format(time.RFC3339))
	}
	if entity.IncidentResolvedTime.Format(time.RFC3339) != "2026-04-28T03:13:01Z" {
		t.Fatalf("unexpected normalized resolve time: %s", entity.IncidentResolvedTime.Format(time.RFC3339))
	}
}
