package cmd

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"
)

type ModelUsage struct {
	NumRequests      int     `json:"numRequests"`
	NumRequestsTotal int     `json:"numRequestsTotal"`
	NumTokens        int     `json:"numTokens"`
	MaxTokenUsage    *int    `json:"maxTokenUsage"`
	MaxRequestUsage  *int    `json:"maxRequestUsage"`
}

type UsageData struct {
	StartOfMonth string                `json:"startOfMonth"`
	Models       map[string]ModelUsage `json:"-"`
}

type StripeProfile struct {
	MembershipType        string  `json:"membershipType"`
	SubscriptionStatus    string  `json:"subscriptionStatus"`
	DaysRemainingOnTrial  *int    `json:"daysRemainingOnTrial"`
	IsTeamMember          bool    `json:"isTeamMember"`
	IsYearlyPlan          bool    `json:"isYearlyPlan"`
}

func DecodeJWTPayload(token string) map[string]interface{} {
	parts := strings.Split(token, ".")
	if len(parts) < 2 {
		return nil
	}
	padded := strings.ReplaceAll(parts[1], "-", "+")
	padded = strings.ReplaceAll(padded, "_", "/")
	data, err := base64.StdEncoding.DecodeString(padded + strings.Repeat("=", (4-len(padded)%4)%4))
	if err != nil {
		return nil
	}
	var result map[string]interface{}
	if err := json.Unmarshal(data, &result); err != nil {
		return nil
	}
	return result
}

func TokenSub(token string) string {
	payload := DecodeJWTPayload(token)
	if payload == nil {
		return ""
	}
	if sub, ok := payload["sub"].(string); ok {
		return sub
	}
	return ""
}

func apiGet(path, token string) (map[string]interface{}, error) {
	client := &http.Client{Timeout: 8 * time.Second}
	req, err := http.NewRequest("GET", "https://api2.cursor.sh"+path, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	var result map[string]interface{}
	if err := json.Unmarshal(data, &result); err != nil {
		return nil, nil
	}
	return result, nil
}

func FetchAccountUsage(token string) (*UsageData, error) {
	raw, err := apiGet("/auth/usage", token)
	if err != nil || raw == nil {
		return nil, err
	}

	startOfMonth, _ := raw["startOfMonth"].(string)
	usage := &UsageData{
		StartOfMonth: startOfMonth,
		Models:       make(map[string]ModelUsage),
	}

	for k, v := range raw {
		if k == "startOfMonth" {
			continue
		}
		data, err := json.Marshal(v)
		if err != nil {
			continue
		}
		var mu ModelUsage
		if err := json.Unmarshal(data, &mu); err == nil {
			usage.Models[k] = mu
		}
	}
	return usage, nil
}

func FetchStripeProfile(token string) (*StripeProfile, error) {
	raw, err := apiGet("/auth/full_stripe_profile", token)
	if err != nil || raw == nil {
		return nil, err
	}

	profile := &StripeProfile{
		MembershipType:     fmt.Sprintf("%v", raw["membershipType"]),
		SubscriptionStatus: fmt.Sprintf("%v", raw["subscriptionStatus"]),
		IsTeamMember:       raw["isTeamMember"] == true,
		IsYearlyPlan:       raw["isYearlyPlan"] == true,
	}
	if d, ok := raw["daysRemainingOnTrial"].(float64); ok {
		di := int(d)
		profile.DaysRemainingOnTrial = &di
	}
	return profile, nil
}

func DescribePlan(profile *StripeProfile) string {
	if profile == nil {
		return ""
	}
	switch profile.MembershipType {
	case "free_trial":
		days := 0
		if profile.DaysRemainingOnTrial != nil {
			days = *profile.DaysRemainingOnTrial
		}
		return fmt.Sprintf("Pro Trial (%dd left) — unlimited fast requests", days)
	case "pro":
		return "Pro — extended limits"
	case "pro_plus":
		return "Pro+ — extended limits"
	case "ultra":
		return "Ultra — extended limits"
	case "free", "hobby":
		return "Hobby (free) — limited agent requests"
	default:
		return fmt.Sprintf("%s · %s", profile.MembershipType, profile.SubscriptionStatus)
	}
}

var modelLabels = map[string]string{
	"gpt-4":                          "Fast Premium Requests",
	"claude-sonnet-4-6":              "Claude Sonnet 4.6",
	"claude-sonnet-4-5-20250929-v1":  "Claude Sonnet 4.5",
	"claude-sonnet-4-20250514-v1":    "Claude Sonnet 4",
	"claude-opus-4-6-v1":             "Claude Opus 4.6",
	"claude-opus-4-5-20251101-v1":    "Claude Opus 4.5",
	"claude-opus-4-1-20250805-v1":    "Claude Opus 4.1",
	"claude-opus-4-20250514-v1":      "Claude Opus 4",
	"claude-haiku-4-5-20251001-v1":   "Claude Haiku 4.5",
	"claude-3-5-haiku-20241022-v1":   "Claude 3.5 Haiku",
	"gpt-5":                          "GPT-5",
	"gpt-4o":                         "GPT-4o",
	"o1":                             "o1",
	"o3-mini":                        "o3-mini",
	"cursor-small":                   "Cursor Small (free)",
}

func modelLabel(key string) string {
	if label, ok := modelLabels[key]; ok {
		return label
	}
	return key
}

func FormatUsageSummary(usage *UsageData) []string {
	if usage == nil {
		return nil
	}
	var lines []string

	start := "?"
	if usage.StartOfMonth != "" {
		if t, err := time.Parse(time.RFC3339, usage.StartOfMonth); err == nil {
			start = t.Format("2006-01-02")
		} else {
			start = usage.StartOfMonth
		}
	}
	lines = append(lines, fmt.Sprintf("     Billing period from %s", start))

	if len(usage.Models) == 0 {
		lines = append(lines, "     No requests this billing period")
		return lines
	}

	type entry struct {
		key   string
		usage ModelUsage
	}
	var entries []entry
	for k, v := range usage.Models {
		entries = append(entries, entry{k, v})
	}

	// Sort: entries with limits first, then by usage descending
	for i := 1; i < len(entries); i++ {
		for j := i; j > 0; j-- {
			a, b := entries[j-1], entries[j]
			aHasLimit := a.usage.MaxRequestUsage != nil
			bHasLimit := b.usage.MaxRequestUsage != nil
			if !aHasLimit && bHasLimit {
				entries[j-1], entries[j] = entries[j], entries[j-1]
			} else if aHasLimit == bHasLimit && a.usage.NumRequests < b.usage.NumRequests {
				entries[j-1], entries[j] = entries[j], entries[j-1]
			} else {
				break
			}
		}
	}

	for _, e := range entries {
		used := e.usage.NumRequests
		max := e.usage.MaxRequestUsage
		label := modelLabel(e.key)
		if max != nil && *max > 0 {
			pct := int(float64(used) / float64(*max) * 100)
			bar := makeBar(used, *max, 12)
			lines = append(lines, fmt.Sprintf("     %s: %d/%d (%d%%) [%s]", label, used, *max, pct, bar))
		} else if used > 0 {
			lines = append(lines, fmt.Sprintf("     %s: %d requests", label, used))
		} else {
			lines = append(lines, fmt.Sprintf("     %s: 0 requests (unlimited)", label))
		}
	}

	return lines
}

func makeBar(used, max, width int) string {
	fill := int(float64(used) / float64(max) * float64(width))
	if fill > width {
		fill = width
	}
	return strings.Repeat("█", fill) + strings.Repeat("░", width-fill)
}
