package handlers

import (
	"net/http"
	"sort"
	"strconv"
	"time"

	"xorm.io/xorm"

	gitdomain "github.com/forgeo/forgebucket/internal/domain/git"
	"github.com/forgeo/forgebucket/internal/models"
)

type TimelineHandler struct {
	db       *xorm.Engine
	repoRoot string
}

func NewTimelineHandler(db *xorm.Engine, repoRoot string) *TimelineHandler {
	return &TimelineHandler{db: db, repoRoot: repoRoot}
}

// ── Event types ───────────────────────────────────────────────────────────────

type timelineEventType string

const (
	eventTypeCommit     timelineEventType = "commit"
	eventTypeRun        timelineEventType = "run"
	eventTypeDeployment timelineEventType = "deployment"
)

// TimelineEvent is a unified, sorted event across commits, CI runs, and deployments.
// The `type` field is used by the frontend to discriminate which fields are present.
type TimelineEvent struct {
	Type      timelineEventType `json:"type"`
	Timestamp time.Time         `json:"timestamp"`

	// commit fields
	SHA     string `json:"sha,omitempty"`
	Message string `json:"message,omitempty"`
	Author  string `json:"author,omitempty"`

	// run fields
	RunID       int64  `json:"runId,omitempty"`
	TriggerRef  string `json:"triggerRef,omitempty"`
	TriggerSHA  string `json:"triggerSha,omitempty"`
	TriggeredBy string `json:"triggeredBy,omitempty"`
	RunStatus   string `json:"runStatus,omitempty"`
	StartedAt   string `json:"startedAt,omitempty"`
	FinishedAt  string `json:"finishedAt,omitempty"`

	// deployment fields
	DeploymentID int64  `json:"deploymentId,omitempty"`
	EnvName      string `json:"envName,omitempty"`
	DeployStatus string `json:"deployStatus,omitempty"`
	DeployedSHA  string `json:"deployedSha,omitempty"`
	DeployRef    string `json:"deployRef,omitempty"`
	Description  string `json:"description,omitempty"`
	RunLink      *int64 `json:"runLink,omitempty"` // links a deployment back to its pipeline run
}

// ── Handler ───────────────────────────────────────────────────────────────────

// GetTimeline returns a merged, time-sorted feed of commits, pipeline runs, and
// deployments for the given repository. Defaults to the last 60 events.
//
// GET /api/v1/repos/:owner/:repo/timeline?limit=60
func (h *TimelineHandler) GetTimeline(w http.ResponseWriter, r *http.Request) {
	limit := 60
	if l, err := strconv.Atoi(r.URL.Query().Get("limit")); err == nil && l > 0 && l <= 200 {
		limit = l
	}

	repo, ok := resolveRepo(h.db, w, r)
	if !ok {
		return
	}

	var events []TimelineEvent

	// ── Commits ───────────────────────────────────────────────────────────────
	gitdomain.SetRepoRoot(h.repoRoot)
	if !gitdomain.IsEmpty(repo.DiskPath) {
		commits, err := gitdomain.Log(repo.DiskPath, repo.DefaultBranch, limit)
		if err == nil {
			for _, c := range commits {
				ts, _ := time.Parse("2006-01-02 15:04:05 -0700", c.Date)
				events = append(events, TimelineEvent{
					Type:      eventTypeCommit,
					Timestamp: ts,
					SHA:       c.Hash,
					Message:   c.Message,
					Author:    c.Author,
				})
			}
		}
	}

	// ── Pipeline runs ─────────────────────────────────────────────────────────
	var runs []models.PipelineRun
	h.db.Where("repo_id = ?", repo.ID).Desc("id").Limit(limit).Find(&runs)
	for _, run := range runs {
		ev := TimelineEvent{
			Type:        eventTypeRun,
			Timestamp:   run.CreatedAt,
			RunID:       run.ID,
			TriggerRef:  run.TriggerRef,
			TriggerSHA:  run.TriggerSHA,
			TriggeredBy: run.TriggeredBy,
			RunStatus:   run.Status,
		}
		if run.StartedAt != nil {
			ev.StartedAt = run.StartedAt.Format(time.RFC3339)
		}
		if run.FinishedAt != nil {
			ev.FinishedAt = run.FinishedAt.Format(time.RFC3339)
		}
		events = append(events, ev)
	}

	// ── Deployments (with environment name) ───────────────────────────────────
	var deploys []models.Deployment
	h.db.Where("repo_id = ?", repo.ID).Desc("id").Limit(limit).Find(&deploys)

	// Cache env names to avoid N+1 queries.
	envNameByID := map[int64]string{}
	for _, d := range deploys {
		if _, ok := envNameByID[d.EnvID]; !ok {
			var env models.Environment
			if found, _ := h.db.ID(d.EnvID).Cols("name").Get(&env); found {
				envNameByID[d.EnvID] = env.Name
			}
		}
		ev := TimelineEvent{
			Type:         eventTypeDeployment,
			Timestamp:    d.CreatedAt,
			DeploymentID: d.ID,
			EnvName:      envNameByID[d.EnvID],
			DeployStatus: string(d.Status),
			DeployedSHA:  d.SHA,
			DeployRef:    d.Ref,
			TriggeredBy:  d.TriggeredBy,
			Description:  d.Description,
			RunLink:      d.RunID,
		}
		events = append(events, ev)
	}

	// ── Merge-sort by timestamp descending ────────────────────────────────────
	sort.Slice(events, func(i, j int) bool {
		return events[i].Timestamp.After(events[j].Timestamp)
	})

	// Cap at limit.
	if len(events) > limit {
		events = events[:limit]
	}
	if events == nil {
		events = []TimelineEvent{}
	}

	jsonOK(w, events)
}
