package types

import (
	"fmt"
	"strings"
	"time"

	"tangled.org/core/api/tangled"
)

type StatusKind string

func (s StatusKind) String() string {
	return string(s)
}

func (s StatusKind) IsFinish() bool {
	switch string(s) {
	case "failed", "timeout", "cancelled", "success":
		return true
	}
	return false
}

func (s StatusKind) IsStart() bool {
	switch string(s) {
	case "pending", "running":
		return true
	}
	return false
}

type WorkflowStatus struct {
	*tangled.CiPipeline_Workflow
	PipelineCreatedAt *string
}

func (w WorkflowStatus) Latest() WorkflowStatus {
	return w
}

func (w WorkflowStatus) Error() string {
	if w.CiPipeline_Workflow == nil || w.CiPipeline_Workflow.Error == nil {
		return ""
	}
	return *w.CiPipeline_Workflow.Error
}

func (w WorkflowStatus) ErrorMessage() string {
	line, _, _ := strings.Cut(w.Error(), "\n")
	return line
}

func (w WorkflowStatus) ErrorDetails() string {
	_, rest, _ := strings.Cut(w.Error(), "\n")
	if rest == "" {
		return ""
	}

	const truncateTo = 15
	lines := strings.Split(rest, "\n")
	if len(lines) <= truncateTo {
		return rest
	}
	return strings.Join(lines[:truncateTo], "\n") + "\n…"
}

func (w WorkflowStatus) Status() StatusKind {
	if w.CiPipeline_Workflow == nil {
		return ""
	}
	return StatusKind(w.CiPipeline_Workflow.Status)
}

func (w WorkflowStatus) TimeTaken() time.Duration {
	if w.CiPipeline_Workflow == nil || w.StartedAt == nil || w.FinishedAt == nil || *w.StartedAt == "" || *w.FinishedAt == "" {
		return 0
	}
	t1, err1 := time.Parse(time.RFC3339, *w.StartedAt)
	t2, err2 := time.Parse(time.RFC3339, *w.FinishedAt)
	if err1 == nil && err2 == nil && t2.After(t1) {
		return t2.Sub(t1)
	}
	return 0
}

func (w WorkflowStatus) Created() time.Time {
	var timeStr string
	if w.CiPipeline_Workflow != nil && w.StartedAt != nil && *w.StartedAt != "" {
		timeStr = *w.StartedAt
	} else if w.CiPipeline_Workflow != nil && w.FinishedAt != nil && *w.FinishedAt != "" {
		timeStr = *w.FinishedAt
	} else if w.PipelineCreatedAt != nil && *w.PipelineCreatedAt != "" {
		timeStr = *w.PipelineCreatedAt
	} else {
		return time.Time{}
	}

	t, err := time.Parse(time.RFC3339, timeStr)
	if err != nil {
		return time.Time{}
	}
	return t
}

type Trigger struct {
	*tangled.CiPipeline_Trigger
}

func (t Trigger) IsPush() bool {
	return t.CiPipeline_Trigger != nil && t.CiPipeline_Trigger.CiTrigger_Push != nil
}

func (t Trigger) IsPullRequest() bool {
	return t.CiPipeline_Trigger != nil && t.CiPipeline_Trigger.CiTrigger_PullRequest != nil
}

func (t Trigger) IsManual() bool {
	return t.CiPipeline_Trigger != nil && t.CiPipeline_Trigger.CiTrigger_Manual != nil
}

func (t Trigger) TargetRef() string {
	if t.CiPipeline_Trigger == nil {
		return ""
	}
	if t.CiPipeline_Trigger.CiTrigger_Push != nil {
		ref := t.CiPipeline_Trigger.CiTrigger_Push.Ref
		if after, ok := strings.CutPrefix(ref, "refs/heads/"); ok {
			return after
		}
		if after, ok := strings.CutPrefix(ref, "refs/tags/"); ok {
			return after
		}
		return ref
	}
	if t.CiPipeline_Trigger.CiTrigger_PullRequest != nil {
		return t.CiPipeline_Trigger.CiTrigger_PullRequest.TargetBranch
	}
	return ""
}

func (t Trigger) PRSourceBranch() string {
	if t.CiPipeline_Trigger == nil || t.CiPipeline_Trigger.CiTrigger_PullRequest == nil {
		return ""
	}
	sb := t.CiPipeline_Trigger.CiTrigger_PullRequest.SourceBranch
	if sb == nil {
		return ""
	}
	return *sb
}

func (t Trigger) PRUri() string {
	if t.CiPipeline_Trigger == nil || t.CiPipeline_Trigger.CiTrigger_PullRequest == nil {
		return ""
	}
	pull := t.CiPipeline_Trigger.CiTrigger_PullRequest.Pull
	if pull == nil {
		return ""
	}
	return *pull
}

type Pipeline struct {
	*tangled.CiPipeline
}

func (p Pipeline) Valid() bool {
	return p.CiPipeline != nil
}

func (p Pipeline) Id() string {
	if p.CiPipeline == nil {
		return ""
	}
	return p.CiPipeline.Id
}

func (p Pipeline) Statuses() map[string]WorkflowStatus {
	m := make(map[string]WorkflowStatus)
	if p.CiPipeline != nil {
		for _, w := range p.CiPipeline.Workflows {
			m[w.Name] = WorkflowStatus{
				CiPipeline_Workflow: w,
				PipelineCreatedAt:   p.CreatedAt,
			}
		}
	}
	return m
}

// PipelinesByCommit keeps the first run per commit from newest-first input.
func PipelinesByCommit(pipelines []*tangled.CiPipeline) map[string]Pipeline {
	m := make(map[string]Pipeline, len(pipelines))
	for _, pipeline := range pipelines {
		if pipeline == nil {
			continue
		}
		if _, ok := m[pipeline.Commit]; ok {
			continue
		}
		m[pipeline.Commit] = Pipeline{CiPipeline: pipeline}
	}
	return m
}

func (p Pipeline) Counts() map[string]int {
	m := make(map[string]int)
	if p.CiPipeline != nil {
		for _, w := range p.CiPipeline.Workflows {
			m[w.Status]++
		}
	}
	return m
}

// InProgress reports whether any workflow in the pipeline is still pending or
// running (i.e. the pipeline has not fully settled).
func (p Pipeline) InProgress() bool {
	counts := p.Counts()
	return counts["pending"] > 0 || counts["running"] > 0
}

func (p Pipeline) ShortStatusSummary() string {
	if p.CiPipeline == nil {
		return ""
	}
	counts := p.Counts()
	total := len(p.CiPipeline.Workflows)
	successes := counts["success"]
	return fmt.Sprintf("%d/%d", successes, total)
}

func (p Pipeline) LongStatusSummary() string {
	if p.CiPipeline == nil {
		return ""
	}
	counts := p.Counts()
	total := len(p.CiPipeline.Workflows)
	var parts []string
	states := []string{"success", "failed", "timeout", "cancelled", "running", "pending"}
	for _, state := range states {
		if c, ok := counts[state]; ok {
			parts = append(parts, fmt.Sprintf("%d/%d %s", c, total, state))
		}
	}
	return strings.Join(parts, ", ")
}

func (p Pipeline) TimeTaken() time.Duration {
	if p.CiPipeline == nil {
		return 0
	}
	var s time.Duration
	for _, w := range p.CiPipeline.Workflows {
		s += WorkflowStatus{CiPipeline_Workflow: w}.TimeTaken()
	}
	return s
}

func (p Pipeline) Created() time.Time {
	if p.CiPipeline == nil || p.CreatedAt == nil || *p.CreatedAt == "" {
		return time.Time{}
	}
	t, err := time.Parse(time.RFC3339, *p.CreatedAt)
	if err != nil {
		return time.Time{}
	}
	return t
}

func (p Pipeline) Trigger() Trigger {
	if p.CiPipeline == nil {
		return Trigger{nil}
	}
	return Trigger{p.CiPipeline.Trigger}
}

func (p Pipeline) IsResponding() bool {
	return p.CiPipeline != nil && len(p.CiPipeline.Workflows) > 0
}

func (p Pipeline) Sha() string {
	if p.CiPipeline == nil {
		return ""
	}
	return p.CiPipeline.Commit
}

// where the pipeline commit was checked out from, nil when checked out from repo itself
func (p Pipeline) SourceRepo() *string {
	if p.CiPipeline == nil {
		return nil
	}
	return p.CiPipeline.SourceRepo
}

func (p Pipeline) Workflows() []string {
	var ws []string
	if p.CiPipeline != nil {
		for _, w := range p.CiPipeline.Workflows {
			ws = append(ws, w.Name)
		}
	}
	return ws
}
