package pulls

import (
	"bytes"
	"context"
	"fmt"
	"image"
	"image/color"
	"image/png"
	"log"
	"net/http"

	"tangled.org/core/appview/models"
	"tangled.org/core/appview/ogcard"
	"tangled.org/core/patchutil"
	"tangled.org/core/types"
)

func (s *Pulls) drawPullSummaryCard(pull *models.Pull, repo *models.Repo, diffStats types.DiffFileStat, filesChanged int) (*ogcard.Card, error) {
	width, height := ogcard.DefaultSize()
	mainCard, err := ogcard.NewCard(width, height)
	if err != nil {
		return nil, err
	}

	// Split: content area (75%) and status/stats area (25%)
	contentCard, statsArea := mainCard.Split(false, 75)

	// Add padding to content
	contentCard.SetMargin(50)

	// Split content horizontally: main content (80%) and avatar area (20%)
	mainContent, avatarArea := contentCard.Split(true, 80)

	// Add margin to main content
	mainContent.SetMargin(10)

	// Use full main content area for repo name and title
	bounds := mainContent.Img.Bounds()
	startX := bounds.Min.X + mainContent.Margin
	startY := bounds.Min.Y + mainContent.Margin

	// Draw full repository name at top (owner/repo format)
	var repoOwner string
	owner, err := s.idResolver.ResolveIdent(context.Background(), repo.Did)
	if err != nil {
		repoOwner = repo.Did
	} else {
		repoOwner = "@" + owner.Handle.String()
	}

	fullRepoName := repoOwner + " / " + repo.Name
	if len(fullRepoName) > 60 {
		fullRepoName = fullRepoName[:60] + "…"
	}

	grayColor := color.RGBA{88, 96, 105, 255}
	err = mainContent.DrawTextAt(fullRepoName, startX, startY, grayColor, 36, ogcard.Top, ogcard.Left)
	if err != nil {
		return nil, err
	}

	// Draw pull request title below repo name with wrapping
	titleY := startY + 60
	titleX := startX

	// Truncate title if too long
	pullTitle := pull.Title
	maxTitleLength := 80
	if len(pullTitle) > maxTitleLength {
		pullTitle = pullTitle[:maxTitleLength] + "…"
	}

	// Create a temporary card for the title area to enable wrapping
	titleBounds := mainContent.Img.Bounds()
	titleWidth := titleBounds.Dx() - (startX - titleBounds.Min.X) - 20   // Leave some margin
	titleHeight := titleBounds.Dy() - (titleY - titleBounds.Min.Y) - 100 // Leave space for pull ID

	titleRect := image.Rect(titleX, titleY, titleX+titleWidth, titleY+titleHeight)
	titleCard := &ogcard.Card{
		Img:    mainContent.Img.SubImage(titleRect).(*image.RGBA),
		Font:   mainContent.Font,
		Margin: 0,
	}

	// Draw wrapped title
	lines, err := titleCard.DrawText(pullTitle, color.Black, 54, ogcard.Top, ogcard.Left)
	if err != nil {
		return nil, err
	}

	// Calculate where title ends (number of lines * line height)
	lineHeight := 60 // Approximate line height for 54pt font
	titleEndY := titleY + (len(lines) * lineHeight) + 10

	// Draw pull ID in gray below the title
	pullIdText := fmt.Sprintf("#%d", pull.PullId)
	err = mainContent.DrawTextAt(pullIdText, startX, titleEndY, grayColor, 54, ogcard.Top, ogcard.Left)
	if err != nil {
		return nil, err
	}

	// Get pull author handle (needed for avatar and metadata)
	var authorHandle string
	author, err := s.idResolver.ResolveIdent(context.Background(), pull.OwnerDid)
	if err != nil {
		authorHandle = pull.OwnerDid
	} else {
		authorHandle = "@" + author.Handle.String()
	}

	// Draw avatar circle on the right side
	avatarBounds := avatarArea.Img.Bounds()
	avatarSize := min(avatarBounds.Dx(), avatarBounds.Dy()) - 20 // Leave some margin
	if avatarSize > 220 {
		avatarSize = 220
	}
	avatarX := avatarBounds.Min.X + (avatarBounds.Dx() / 2) - (avatarSize / 2)
	avatarY := avatarBounds.Min.Y + 20

	// Get avatar URL for pull author
	avatarURL := s.pages.AvatarUrl(authorHandle, "256")
	err = avatarArea.DrawCircularExternalImage(avatarURL, avatarX, avatarY, avatarSize)
	if err != nil {
		log.Printf("failed to draw avatar (non-fatal): %v", err)
	}

	// Split stats area: left side for status/stats (80%), right side for dolly (20%)
	statusArea, dollyArea := statsArea.Split(true, 80)

	// Draw status and stats
	statsBounds := statusArea.Img.Bounds()
	statsX := statsBounds.Min.X + 60 // left padding
	statsY := statsBounds.Min.Y

	iconColor := color.RGBA{88, 96, 105, 255}
	iconSize := 36
	textSize := 36.0
	labelSize := 28.0
	iconBaselineOffset := int(textSize) / 2

	// Draw status (open/merged/closed) with colored icon and text
	var statusIcon string
	var statusText string
	var statusColor color.RGBA

	if pull.State.IsOpen() {
		statusIcon = "git-pull-request"
		statusText = "open"
		statusColor = color.RGBA{34, 139, 34, 255} // green
	} else if pull.State.IsMerged() {
		statusIcon = "git-merge"
		statusText = "merged"
		statusColor = color.RGBA{138, 43, 226, 255} // purple
	} else {
		statusIcon = "git-pull-request-closed"
		statusText = "closed"
		statusColor = color.RGBA{52, 58, 64, 255} // dark gray
	}

	statusTextWidth := statusArea.TextWidth(statusText, textSize)
	badgePadding := 12
	badgeHeight := int(textSize) + (badgePadding * 2)
	badgeWidth := iconSize + badgePadding + statusTextWidth + (badgePadding * 2)
	cornerRadius := 8
	badgeX := 60
	badgeY := 0

	statusArea.DrawRoundedRect(badgeX, badgeY, badgeWidth, badgeHeight, cornerRadius, statusColor)

	whiteColor := color.RGBA{255, 255, 255, 255}
	iconX := statsX + badgePadding
	iconY := statsY + (badgeHeight-iconSize)/2
	err = statusArea.DrawLucideIcon(statusIcon, iconX, iconY, iconSize, whiteColor)
	if err != nil {
		log.Printf("failed to draw status icon: %v", err)
	}

	textX := statsX + badgePadding + iconSize + badgePadding
	textY := statsY + (badgeHeight-int(textSize))/2 - 5
	err = statusArea.DrawTextAt(statusText, textX, textY, whiteColor, textSize, ogcard.Top, ogcard.Left)
	if err != nil {
		log.Printf("failed to draw status text: %v", err)
	}

	currentX := statsX + badgeWidth + 50

	// Draw comment count
	err = statusArea.DrawLucideIcon("message-square", currentX, iconY, iconSize, iconColor)
	if err != nil {
		log.Printf("failed to draw comment icon: %v", err)
	}

	currentX += iconSize + 15
	commentCount := pull.TotalComments()
	commentText := fmt.Sprintf("%d comments", commentCount)
	if commentCount == 1 {
		commentText = "1 comment"
	}
	err = statusArea.DrawTextAt(commentText, currentX, textY, iconColor, textSize, ogcard.Top, ogcard.Left)
	if err != nil {
		log.Printf("failed to draw comment text: %v", err)
	}

	commentTextWidth := len(commentText) * 20
	currentX += commentTextWidth + 40

	// Draw files changed
	err = statusArea.DrawLucideIcon("file-diff", currentX, iconY, iconSize, iconColor)
	if err != nil {
		log.Printf("failed to draw file diff icon: %v", err)
	}

	currentX += iconSize + 15
	filesText := fmt.Sprintf("%d files", filesChanged)
	if filesChanged == 1 {
		filesText = "1 file"
	}
	err = statusArea.DrawTextAt(filesText, currentX, textY, iconColor, textSize, ogcard.Top, ogcard.Left)
	if err != nil {
		log.Printf("failed to draw files text: %v", err)
	}

	filesTextWidth := len(filesText) * 20
	currentX += filesTextWidth

	// Draw additions (green +)
	greenColor := color.RGBA{34, 139, 34, 255}
	additionsText := fmt.Sprintf("+%d", diffStats.Insertions)
	err = statusArea.DrawTextAt(additionsText, currentX, textY, greenColor, textSize, ogcard.Top, ogcard.Left)
	if err != nil {
		log.Printf("failed to draw additions text: %v", err)
	}

	additionsTextWidth := len(additionsText) * 20
	currentX += additionsTextWidth + 30

	// Draw deletions (red -) right next to additions
	redColor := color.RGBA{220, 20, 60, 255}
	deletionsText := fmt.Sprintf("-%d", diffStats.Deletions)
	err = statusArea.DrawTextAt(deletionsText, currentX, textY, redColor, textSize, ogcard.Top, ogcard.Left)
	if err != nil {
		log.Printf("failed to draw deletions text: %v", err)
	}

	// Draw dolly logo on the right side
	dollyBounds := dollyArea.Img.Bounds()
	dollySize := 90
	dollyX := dollyBounds.Min.X + (dollyBounds.Dx() / 2) - (dollySize / 2)
	dollyY := statsY + iconBaselineOffset - dollySize/2 + 25
	dollyColor := color.RGBA{180, 180, 180, 255} // light gray
	err = dollyArea.DrawDolly(dollyX, dollyY, dollySize, dollyColor)
	if err != nil {
		log.Printf("dolly silhouette not available (this is ok): %v", err)
	}

	// Draw "opened by @author" and date at the bottom with more spacing
	labelY := statsY + iconSize + 30

	// Format the opened date
	openedDate := pull.Created.Format("Jan 2, 2006")
	metaText := fmt.Sprintf("opened by %s · %s", authorHandle, openedDate)

	err = statusArea.DrawTextAt(metaText, statsX, labelY, iconColor, labelSize, ogcard.Top, ogcard.Left)
	if err != nil {
		log.Printf("failed to draw metadata: %v", err)
	}

	return mainCard, nil
}

func (s *Pulls) PullOpenGraphSummary(w http.ResponseWriter, r *http.Request) {
	f, err := s.repoResolver.Resolve(r)
	if err != nil {
		log.Println("failed to get repo and knot", err)
		return
	}

	pull, ok := r.Context().Value("pull").(*models.Pull)
	if !ok {
		log.Println("pull not found in context")
		http.Error(w, "pull not found", http.StatusNotFound)
		return
	}

	// Calculate diff stats from latest submission using patchutil
	var diffStats types.DiffFileStat
	filesChanged := 0
	if len(pull.Submissions) > 0 {
		latestSubmission := pull.LatestSubmission()
		niceDiff := patchutil.AsNiceDiff(latestSubmission.Patch, pull.TargetBranch)
		diffStats.Insertions = int64(niceDiff.Stat.Insertions)
		diffStats.Deletions = int64(niceDiff.Stat.Deletions)
		filesChanged = niceDiff.Stat.FilesChanged
	}

	card, err := s.drawPullSummaryCard(pull, f, diffStats, filesChanged)
	if err != nil {
		log.Println("failed to draw pull summary card", err)
		http.Error(w, "failed to draw pull summary card", http.StatusInternalServerError)
		return
	}

	var imageBuffer bytes.Buffer
	err = png.Encode(&imageBuffer, card.Img)
	if err != nil {
		log.Println("failed to encode pull summary card", err)
		http.Error(w, "failed to encode pull summary card", http.StatusInternalServerError)
		return
	}

	imageBytes := imageBuffer.Bytes()

	w.Header().Set("Content-Type", "image/png")
	w.Header().Set("Cache-Control", "public, max-age=3600") // 1 hour
	w.WriteHeader(http.StatusOK)
	_, err = w.Write(imageBytes)
	if err != nil {
		log.Println("failed to write pull summary card", err)
		return
	}
}
