package source

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

	relent "github.com/tidbcloud/devops/entity/release"
	"github.com/tidbcloud/devops/pkg/infra/clients/jiracli"
	"github.com/tidbcloud/devops/pkg/infra/utils/pointer"

	"github.com/andygrunwald/go-jira"
)

var jiraIssueInterestFields = []string{"status", "fixVersions", "summary", "assignee", "updated"}

var _ Source = &SourceJira{}

type SourceJira struct {
	cli     jiracli.JiraClient
	project string
}

func NewSourceJira(cli jiracli.JiraClient, project string) *SourceJira {
	return &SourceJira{
		cli:     cli,
		project: project,
	}
}

func (s *SourceJira) ListReleases(ctx context.Context) ([]*relent.ReleaseInRepo, error) {
	versions, err := s.cli.ListVersions(ctx, s.project)
	if err != nil {
		return nil, fmt.Errorf("list releases failed: %w", err)
	}

	releases := make([]*relent.ReleaseInRepo, 0, len(versions))
	for _, version := range versions {
		rel, err := s.convertToRelease(version)
		if err != nil {
			continue
		}
		releases = append(releases, rel)
	}

	return releases, nil
}

func (s *SourceJira) SearchTickets(ctx context.Context, opts SearchTicketOptions) ([]string, error) {
	jqlClauses := []string{fmt.Sprintf(`project = "%s"`, s.project)}

	if opts.UpdatedAtGTE != nil && !opts.UpdatedAtGTE.IsZero() {
		jqlClauses = append(jqlClauses, fmt.Sprintf(`updated >= "%s"`, opts.UpdatedAtGTE.UTC().Format("2006-01-02 15:04")))
	}

	jql := fmt.Sprintf(`%s ORDER BY updated DESC`, strings.Join(jqlClauses, " AND "))

	pageToken := ""
	tickets := make([]string, 0, 50)
	for {
		result, err := s.cli.SearchIssues(ctx, jql, &jiracli.SearchOptions{
			NextPageToken: pageToken,
			MaxResults:    50,
			Fields:        jiraIssueInterestFields,
		})
		if err != nil {
			return nil, fmt.Errorf("search tickets failed: %w", err)
		}

		for _, issue := range result.Issues {
			tickets = append(tickets, issue.Key)
		}

		if result.IsLast || result.NextPageToken == "" {
			break
		}
		pageToken = result.NextPageToken
	}

	return tickets, nil
}

func (s *SourceJira) GetTicket(ctx context.Context, name string) (relent.ReleaseTicketInRepo, error) {
	issue, err := s.cli.GetIssue(ctx, name, &jira.GetQueryOptions{Fields: strings.Join(jiraIssueInterestFields, ",")})
	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 *SourceJira) UpdateTicketStatus(ctx context.Context, name string, status string) error {
	if err := s.cli.UpdateIssueStatus(ctx, name, status); err != nil {
		return fmt.Errorf("update ticket status failed: %w", err)
	}

	return nil
}

func (s *SourceJira) convertToRelease(version jira.Version) (*relent.ReleaseInRepo, error) {
	name, date, typ, err := s.convertFixVersionToName(version.Name)
	if err != nil {
		return nil, err
	}

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

	if pointer.Deref(version.Released, false) {
		release.Status = relent.ReleaseStatusFinished
	}

	switch typ {
	case relent.ReleaseTypePeriodic:
		// Now release window is is from Monday to Friday.
		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 *SourceJira) convertFixVersionToName(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 *SourceJira) convertToReleaseTicket(issue jira.Issue) (*relent.ReleaseTicketInRepo, error) {
	ticket := &relent.ReleaseTicketInRepo{
		Name:          issue.Key,
		Summary:       issue.Fields.Summary,
		Status:        issue.Fields.Status.Name,
		Type:          relent.ReleaseTicketTypeDM,
		AssigneeEmail: "",
		ReleaseName:   nil,
	}

	if issue.Fields.Assignee != nil {
		ticket.AssigneeEmail = issue.Fields.Assignee.EmailAddress
	}

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

	return ticket, nil
}
