package handlers

import (
	"net/http"

	"xorm.io/xorm"

	"github.com/forgeo/forgebucket/internal/api/middleware"
	"github.com/forgeo/forgebucket/internal/models"
)

type DashboardHandler struct{ db *xorm.Engine }

func NewDashboardHandler(db *xorm.Engine) *DashboardHandler { return &DashboardHandler{db: db} }

// ── Response shapes ───────────────────────────────────────────────────────────

type dashStats struct {
	RepoCount   int `json:"repoCount"`
	OpenPRs     int `json:"openPRs"`
	ReviewQueue int `json:"reviewQueue"`
	OpenIssues  int `json:"openIssues"`
}

type dashPR struct {
	ID           int64  `json:"id"`
	Title        string `json:"title"`
	SourceBranch string `json:"sourceBranch"`
	TargetBranch string `json:"targetBranch"`
	Status       string `json:"status"`
	CreatedAt    string `json:"createdAt"`
	UpdatedAt    string `json:"updatedAt"`
	RepoID       int64  `json:"repoId"`
	RepoName     string `json:"repoName"`
	OwnerName    string `json:"ownerName"`
	AuthorID     int64  `json:"authorId"`
}

type dashIssue struct {
	ID        int64  `json:"id"`
	Number    int    `json:"number"`
	Title     string `json:"title"`
	State     string `json:"state"`
	CreatedAt string `json:"createdAt"`
	UpdatedAt string `json:"updatedAt"`
	RepoID    int64  `json:"repoId"`
	RepoName  string `json:"repoName"`
	OwnerName string `json:"ownerName"`
}

type dashRepo struct {
	ID             int64  `json:"id"`
	Name           string `json:"name"`
	Description    string `json:"description"`
	IsPrivate      bool   `json:"isPrivate"`
	DefaultBranch  string `json:"defaultBranch"`
	UpdatedAt      string `json:"updatedAt"`
	OwnerName      string `json:"ownerName"`
	AvatarURL      string `json:"avatarUrl"`
	OpenPRCount    int    `json:"openPrCount"`
	OpenIssueCount int    `json:"openIssueCount"`
}

type dashRun struct {
	ID          int64   `json:"id"`
	RepoID      int64   `json:"repoId"`
	RepoName    string  `json:"repoName"`
	OwnerName   string  `json:"ownerName"`
	TriggerRef  string  `json:"triggerRef"`
	TriggerSHA  string  `json:"triggerSha"`
	TriggeredBy string  `json:"triggeredBy"`
	Status      string  `json:"status"`
	StartedAt   *string `json:"startedAt"`
	FinishedAt  *string `json:"finishedAt"`
	CreatedAt   string  `json:"createdAt"`
}

type dashboardResponse struct {
	Stats        dashStats   `json:"stats"`
	ReviewQueue  []dashPR    `json:"reviewQueue"`
	MyOpenPRs    []dashPR    `json:"myOpenPRs"`
	MyOpenIssues []dashIssue `json:"myOpenIssues"`
	Repos        []dashRepo  `json:"repos"`
	RecentRuns   []dashRun   `json:"recentRuns"`
}

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

func (h *DashboardHandler) Get(w http.ResponseWriter, r *http.Request) {
	userID, _ := middleware.UserIDFromContext(r.Context())

	// 1. Repos owned by this user (user-level) + workspace repos where user is a member.
	var repos []models.Repository
	h.db.Where("owner_id = ? AND workspace_id IS NULL", userID).Desc("updated_at").Find(&repos)

	var memberships []models.WorkspaceMember
	h.db.Where("user_id = ?", userID).Find(&memberships)
	if len(memberships) > 0 {
		wsIDs := make([]int64, len(memberships))
		for i, m := range memberships {
			wsIDs[i] = m.WorkspaceID
		}
		var wsRepos []models.Repository
		h.db.In("workspace_id", wsIDs).Desc("updated_at").Find(&wsRepos)
		repos = append(repos, wsRepos...)
	}

	repoIDs := make([]int64, len(repos))
	repoByID := make(map[int64]models.Repository, len(repos))
	for i, rp := range repos {
		repoIDs[i] = rp.ID
		repoByID[rp.ID] = rp
	}

	// Owner username — needed for URLs.
	var owner models.User
	h.db.ID(userID).Cols("id", "username", "avatar_url").Get(&owner)

	// 2. All open PRs across user repos.
	var allOpenPRs []models.PullRequest
	if len(repoIDs) > 0 {
		h.db.In("repo_id", repoIDs).Where("status = 'open'").Desc("updated_at").Find(&allOpenPRs)
	}

	// 3. PRs where user is assigned as reviewer (and PR is open).
	var reviewerRows []models.PrReviewer
	h.db.Where("user_id = ?", userID).Find(&reviewerRows)
	reviewPRIDs := make([]int64, 0, len(reviewerRows))
	for _, rv := range reviewerRows {
		reviewPRIDs = append(reviewPRIDs, rv.PRID)
	}
	var reviewPRs []models.PullRequest
	if len(reviewPRIDs) > 0 {
		h.db.In("id", reviewPRIDs).Where("status = 'open' AND author_id != ?", userID).
			Desc("updated_at").Find(&reviewPRs)
	}

	// 4. Open issues authored by user across their repos.
	var openIssues []models.Issue
	if len(repoIDs) > 0 {
		h.db.In("repo_id", repoIDs).Where("author_id = ? AND state = 'open'", userID).
			Desc("updated_at").Limit(20).Find(&openIssues)
	}

	// 5. Build per-repo counters.
	prCountByRepo := make(map[int64]int)
	for _, pr := range allOpenPRs {
		prCountByRepo[pr.RepoID]++
	}
	issueCountByRepo := make(map[int64]int)
	for _, iss := range openIssues {
		issueCountByRepo[iss.RepoID]++
	}

	// 6. Separate my PRs from the full open list.
	var myOpenPRs []models.PullRequest
	for _, pr := range allOpenPRs {
		if pr.AuthorID == userID {
			myOpenPRs = append(myOpenPRs, pr)
		}
	}

	// ── Build response ─────────────────────────────────────────────────────────

	toDashPR := func(pr models.PullRequest) dashPR {
		rp := repoByID[pr.RepoID]
		return dashPR{
			ID:           pr.ID,
			Title:        pr.Title,
			SourceBranch: pr.SourceBranch,
			TargetBranch: pr.TargetBranch,
			Status:       string(pr.Status),
			CreatedAt:    pr.CreatedAt.Format("2006-01-02T15:04:05Z"),
			UpdatedAt:    pr.UpdatedAt.Format("2006-01-02T15:04:05Z"),
			RepoID:       pr.RepoID,
			RepoName:     rp.Name,
			OwnerName:    owner.Username,
			AuthorID:     pr.AuthorID,
		}
	}

	// For review queue PRs that may be on OTHER users' repos, look up owner.
	toReviewPR := func(pr models.PullRequest) dashPR {
		rp := repoByID[pr.RepoID]
		dp := toDashPR(pr)
		if rp.ID == 0 {
			// PR is on a repo the user doesn't own (they're just a member).
			var foreignRepo models.Repository
			if found, _ := h.db.ID(pr.RepoID).Get(&foreignRepo); found {
				var foreignOwner models.User
				h.db.ID(foreignRepo.OwnerID).Cols("username").Get(&foreignOwner)
				dp.RepoName = foreignRepo.Name
				dp.OwnerName = foreignOwner.Username
			}
		}
		return dp
	}

	// Cache workspace handles to avoid N+1.
	wsHandleByID := map[int64]string{}

	dashRepos := make([]dashRepo, 0, len(repos))
	for _, rp := range repos {
		ownerName := owner.Username
		if rp.WorkspaceID != nil && *rp.WorkspaceID != 0 {
			if wsHandle, ok := wsHandleByID[*rp.WorkspaceID]; ok {
				ownerName = wsHandle
			} else {
				var ws models.Workspace
				h.db.ID(*rp.WorkspaceID).Cols("handle").Get(&ws)
				wsHandleByID[*rp.WorkspaceID] = ws.Handle
				ownerName = ws.Handle
			}
		}
		dashRepos = append(dashRepos, dashRepo{
			ID:             rp.ID,
			Name:           rp.Name,
			Description:    rp.Description,
			IsPrivate:      rp.IsPrivate,
			DefaultBranch:  rp.DefaultBranch,
			UpdatedAt:      rp.UpdatedAt.Format("2006-01-02T15:04:05Z"),
			OwnerName:      ownerName,
			AvatarURL:      "/api/v1/repos/" + ownerName + "/" + rp.Name + "/avatar",
			OpenPRCount:    prCountByRepo[rp.ID],
			OpenIssueCount: issueCountByRepo[rp.ID],
		})
	}

	myPRDash := make([]dashPR, 0, len(myOpenPRs))
	for _, pr := range myOpenPRs {
		myPRDash = append(myPRDash, toDashPR(pr))
	}

	reviewQueue := make([]dashPR, 0, len(reviewPRs))
	for _, pr := range reviewPRs {
		reviewQueue = append(reviewQueue, toReviewPR(pr))
	}

	issueDash := make([]dashIssue, 0, len(openIssues))
	for _, iss := range openIssues {
		rp := repoByID[iss.RepoID]
		issueDash = append(issueDash, dashIssue{
			ID:        iss.ID,
			Number:    iss.Number,
			Title:     iss.Title,
			State:     string(iss.State),
			CreatedAt: iss.CreatedAt.Format("2006-01-02T15:04:05Z"),
			UpdatedAt: iss.UpdatedAt.Format("2006-01-02T15:04:05Z"),
			RepoID:    iss.RepoID,
			RepoName:  rp.Name,
			OwnerName: owner.Username,
		})
	}

	// 7. Recent CI runs across user repos.
	var recentRuns []models.PipelineRun
	if len(repoIDs) > 0 {
		h.db.In("repo_id", repoIDs).Desc("id").Limit(5).Find(&recentRuns)
	}
	runsDash := make([]dashRun, 0, len(recentRuns))
	for _, run := range recentRuns {
		rp := repoByID[run.RepoID]
		dr := dashRun{
			ID:          run.ID,
			RepoID:      run.RepoID,
			RepoName:    rp.Name,
			OwnerName:   owner.Username,
			TriggerRef:  run.TriggerRef,
			TriggerSHA:  run.TriggerSHA,
			TriggeredBy: run.TriggeredBy,
			Status:      run.Status,
			CreatedAt:   run.CreatedAt.Format("2006-01-02T15:04:05Z"),
		}
		if run.StartedAt != nil {
			s := run.StartedAt.Format("2006-01-02T15:04:05Z")
			dr.StartedAt = &s
		}
		if run.FinishedAt != nil {
			f := run.FinishedAt.Format("2006-01-02T15:04:05Z")
			dr.FinishedAt = &f
		}
		runsDash = append(runsDash, dr)
	}

	resp := dashboardResponse{
		Stats: dashStats{
			RepoCount:   len(repos),
			OpenPRs:     len(myOpenPRs),
			ReviewQueue: len(reviewPRs),
			OpenIssues:  len(openIssues),
		},
		ReviewQueue:  reviewQueue,
		MyOpenPRs:    myPRDash,
		MyOpenIssues: issueDash,
		Repos:        dashRepos,
		RecentRuns:   runsDash,
	}
	jsonOK(w, resp)
}
