package source

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"

	relent "github.com/tidbcloud/devops/entity/release"
	"github.com/tidbcloud/devops/pkg/infra/clients/onescli"
	onesapi "github.com/tidbcloud/devops/pkg/infra/clients/onescli/api"
	"github.com/tidbcloud/devops/pkg/infra/utils/container"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"
)

var _ Source = &SourceOnes{}

var onesIssueStatusMapping = map[string]string{
	onesapi.IssueStatusTODO:            relent.ReleaseTicketStatusTODO,
	onesapi.IssueStatusInDev:           relent.ReleaseTicketStatusInDev,
	onesapi.IssueStatusReadyForRelease: relent.ReleaseTicketStatusReady,
	onesapi.IssueStatusDone:            relent.ReleaseTicketStatusDone,
	onesapi.IssueStatusCanceled:        relent.ReleaseTicketStatusCanceled,
}

type SourceOnes struct {
	cli     onescli.OnesClient
	project string
}

func NewSourceOnes(cli onescli.OnesClient, project string) *SourceOnes {
	return &SourceOnes{
		cli:     cli,
		project: project,
	}
}

func (s *SourceOnes) ListReleases(ctx context.Context) ([]*relent.ReleaseInRepo, error) {
	issues, err := s.cli.SearchIssues(ctx, onescli.SearchIssuesParam{
		ProjectIs:    pointer.Pointer(s.project),
		IssueTypeIn:  pointer.Pointer([]string{onesapi.IssueTypeRelease}),
		CreatedAtGTE: pointer.Pointer(time.Now().AddDate(0, -1, 0)), // list releases created in the last month
	})
	if err != nil {
		return nil, fmt.Errorf("list releases failed: %w", err)
	}

	releaseMap := map[string]*relent.ReleaseInRepo{}
	for _, issue := range issues {
		rel, convErr := s.convertToRelease(issue.Title)
		if convErr != nil {
			continue
		}
		releaseMap[rel.Name] = rel
	}

	releases := make([]*relent.ReleaseInRepo, 0, len(releaseMap))
	for _, rel := range releaseMap {
		releases = append(releases, rel)
	}
	sort.Slice(releases, func(i, j int) bool {
		return releases[i].Name > releases[j].Name
	})

	return releases, nil
}

func (s *SourceOnes) SearchTickets(ctx context.Context, opts SearchTicketOptions) ([]string, error) {
	param := onescli.SearchIssuesParam{
		ProjectIs:      pointer.Pointer(s.project),
		IssueTypeNotIn: pointer.Pointer([]string{onesapi.IssueTypeRelease}),
		UpdatedAtGTE:   opts.UpdatedAtGTE,
	}

	issues, err := s.cli.SearchIssues(ctx, param)
	if err != nil {
		return nil, fmt.Errorf("search tickets failed: %w", err)
	}

	names := container.TransformSlice(issues, func(issue *onesapi.SearchIssue) string { return issue.DisplayID })
	return names, nil
}

func (s *SourceOnes) GetTicket(ctx context.Context, name string) (relent.ReleaseTicketInRepo, error) {
	issue, err := s.cli.GetIssue(ctx, name)
	if err != nil {
		return relent.ReleaseTicketInRepo{}, fmt.Errorf("get ticket failed: %w", err)
	}

	ticket, err := s.convertToReleaseTicket(*issue)
	if err != nil {
		return relent.ReleaseTicketInRepo{}, err
	}

	return *ticket, nil
}

func (s *SourceOnes) UpdateTicketStatus(ctx context.Context, id string, status string) error {
	issueStatus := status
	for onesStatus, mappedStatus := range onesIssueStatusMapping {
		if mappedStatus == status {
			issueStatus = onesStatus
			break
		}
	}

	if err := s.cli.UpdateIssueStatus(ctx, id, issueStatus); err != nil {
		return err
	}

	return nil
}

func (s *SourceOnes) convertToRelease(src string) (*relent.ReleaseInRepo, error) {
	name, date, typ, err := s.convertPublishVersionToName(src)
	if err != nil {
		return nil, err
	}

	release := &relent.ReleaseInRepo{
		ID:     0,
		Name:   name,
		Status: relent.ReleaseStatusBeforeRelease,
		Type:   typ,
	}

	now := time.Now().UTC()
	if date.Before(now) {
		release.Status = relent.ReleaseStatusFinished
	}

	switch typ {
	case relent.ReleaseTypePeriodic:
		release.WindowStartAt = time.Date(date.Year(), date.Month(), date.Day()-1,
			0, 0, 0, 0, time.UTC)
		release.WindowEndAt = time.Date(date.Year(), date.Month(), date.Day()+5,
			23, 59, 59, 0, time.UTC)
	default:
		return nil, fmt.Errorf("unknown release type: %s", name)
	}

	return release, nil
}

func (s *SourceOnes) convertPublishVersionToName(src string) (name string, date time.Time, typ relent.ReleaseType, err error) {
	name = strings.ToLower(src)

	fields := strings.Split(name, "-")
	if len(fields) != 2 {
		err = fmt.Errorf("invalid release name: %s", name)
		return
	}
	date, err = time.Parse("20060102", fields[1])
	if err != nil {
		err = fmt.Errorf("invalid release date: %s", name)
		return
	}

	switch fields[0] {
	case "release":
		typ = relent.ReleaseTypePeriodic
	default:
		err = fmt.Errorf("invalid release type: %s", name)
	}

	return
}

func (s *SourceOnes) convertToReleaseTicket(issue onesapi.SearchIssue) (*relent.ReleaseTicketInRepo, error) {
	ticketStatus := onesIssueStatusMapping[issue.Status.Name]
	if ticketStatus == "" {
		ticketStatus = issue.Status.Name
	}

	ticket := &relent.ReleaseTicketInRepo{
		Name:          issue.DisplayID,
		Summary:       issue.Title,
		Status:        ticketStatus,
		Type:          relent.ReleaseTicketTypeDM,
		AssigneeEmail: issue.Assignee.Email,
		ReleaseName:   nil,
	}

	if len(issue.PublishVersion) != 0 {
		name, _, _, err := s.convertPublishVersionToName(issue.PublishVersion[0].Name)
		if err != nil {
			return nil, fmt.Errorf("invalid release name: %s", issue.PublishVersion[0].Name)
		}
		ticket.ReleaseName = &name
	}

	return ticket, nil
}
