package main

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/charmbracelet/bubbles/viewport"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	pbhuman "github.com/flowy-live/admin-cli/genproto/helios/human"
	pbkeypad "github.com/flowy-live/admin-cli/genproto/helios/keypad"
	"github.com/mergestat/timediff"
	"github.com/sirupsen/logrus"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/metadata"
	"net/http"
)

type MetricResponse struct {
	Metrics []struct {
		ID          string  `json:"id"`
		Name        string  `json:"name"`
		Description string  `json:"description"`
		Value       float64 `json:"value"`
		Unit        string  `json:"unit"`
		Period      string  `json:"period"`
	} `json:"metrics"`
}

type SubscriptionResponse struct {
	Items []struct {
		Status       string `json:"status"`
		GivesAccess  bool   `json:"gives_access"`
		Entitlements struct {
			Items []struct {
				LookupKey string `json:"lookup_key"`
			} `json:"items"`
		} `json:"entitlements"`
	} `json:"items"`
}

func getRevenue() string {
	client := &http.Client{}

	req, err := http.NewRequest("GET", "https://api.revenuecat.com/v2/projects/proj5b2f7356/metrics/overview", nil)
	if err != nil {
		return fmt.Sprintf("Error creating request: %v", err)
	}

	// Add authorization header
	req.Header.Add("Authorization", "Bearer sk_wxmjNCLtnDSUPmSXlbEWyuqkpTVTi")

	response, err := client.Do(req)
	if err != nil {
		return fmt.Sprintf("Error fetching revenue: %v", err)
	}
	defer response.Body.Close()

	body, err := io.ReadAll(response.Body)
	if err != nil {
		return fmt.Sprintf("Error reading response: %v", err)
	}

	var metricResp MetricResponse
	err = json.Unmarshal(body, &metricResp)
	if err != nil {
		return fmt.Sprintf("Error parsing JSON: %v", err)
	}

	// Create a map for easy lookup
	metrics := make(map[string]struct {
		name  string
		value float64
		unit  string
	})

	for _, m := range metricResp.Metrics {
		metrics[m.ID] = struct {
			name  string
			value float64
			unit  string
		}{
			name:  m.Name,
			value: m.Value,
			unit:  m.Unit,
		}
	}
	if m, ok := metrics["revenue"]; ok {
		return fmt.Sprintf("%s%.0f", m.unit, m.value)
	}
	return ""

	// Format the output
	// var result strings.Builder
	// result.WriteString("\n")
	// result.WriteString("💰 Revenue Metrics\n")
	// result.WriteString("═══════════════════════════════════════\n\n")

	// Key metrics
	// if m, ok := metrics["mrr"]; ok {
	// 	result.WriteString(fmt.Sprintf("MRR:                  %s%.0f\n", m.unit, m.value))
	// }
	// if m, ok := metrics["revenue"]; ok {
	// 	result.WriteString(fmt.Sprintf("Revenue (28d):        %s%.0f\n", m.unit, m.value))
	// }
	// result.WriteString("\n")
	//
	// if m, ok := metrics["active_subscriptions"]; ok {
	// 	result.WriteString(fmt.Sprintf("Active Subscriptions: %s%.0f\n", m.unit, m.value))
	// }
	// if m, ok := metrics["active_trials"]; ok {
	// 	result.WriteString(fmt.Sprintf("Active Trials:        %s%.0f\n", m.unit, m.value))
	// }
	// result.WriteString("\n")
	//
	// if m, ok := metrics["new_customers"]; ok {
	// 	result.WriteString(fmt.Sprintf("New Customers (28d):  %s%.0f\n", m.unit, m.value))
	// }
	// if m, ok := metrics["active_users"]; ok {
	// 	result.WriteString(fmt.Sprintf("Active Users (28d):   %s%.0f\n", m.unit, m.value))
	// }
	//
	// result.WriteString("\n═══════════════════════════════════════\n")
	//
	// return result.String()
}

func hasProSubscription(customerEmail string) bool {
	client := &http.Client{}

	url := fmt.Sprintf("https://api.revenuecat.com/v2/projects/proj5b2f7356/customers/%s/subscriptions", customerEmail)
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		logrus.Errorf("Error creating subscription request for %s: %v", customerEmail, err)
		return false
	}

	// Add authorization header
	req.Header.Add("Authorization", "Bearer sk_wxmjNCLtnDSUPmSXlbEWyuqkpTVTi")

	response, err := client.Do(req)
	if err != nil {
		logrus.Errorf("Error fetching subscriptions for %s: %v", customerEmail, err)
		return false
	}
	defer response.Body.Close()

	// If customer not found or no subscriptions, return false
	if response.StatusCode != 200 {
		return false
	}

	body, err := io.ReadAll(response.Body)
	if err != nil {
		logrus.Errorf("Error reading subscription response for %s: %v", customerEmail, err)
		return false
	}

	var subResp SubscriptionResponse
	err = json.Unmarshal(body, &subResp)
	if err != nil {
		logrus.Errorf("Error parsing subscription JSON for %s: %v", customerEmail, err)
		return false
	}

	// Check if any subscription is active and has Pro entitlement
	for _, sub := range subResp.Items {
		if sub.Status == "active" && sub.GivesAccess {
			for _, ent := range sub.Entitlements.Items {
				if ent.LookupKey == "Pro" {
					return true
				}
			}
		}
	}

	return false
}

var (
	titleStyle = lipgloss.NewStyle().
			Bold(true).
			Foreground(lipgloss.Color("#7D56F4")).
			Padding(0, 1).
			MarginBottom(1)

	selectedItemStyle = lipgloss.NewStyle().
				Foreground(lipgloss.Color("#FFFFFF")).
				Background(lipgloss.Color("#7D56F4")).
				Bold(true).
				Padding(0, 1)

	normalItemStyle = lipgloss.NewStyle().
			Foreground(lipgloss.Color("#FAFAFA")).
			Padding(0, 1)

	dimItemStyle = lipgloss.NewStyle().
			Foreground(lipgloss.Color("#666666")).
			Padding(0, 1)

	cursorStyle = lipgloss.NewStyle().
			Foreground(lipgloss.Color("#FF75B5")).
			Bold(true)

	helpStyle = lipgloss.NewStyle().
			Foreground(lipgloss.Color("#626262")).
			Padding(1, 0, 0, 1)

	statusStyle = lipgloss.NewStyle().
			Foreground(lipgloss.Color("#7D56F4")).
			Bold(true)

	borderStyle = lipgloss.NewStyle().
			Foreground(lipgloss.Color("#3C3C3C"))

	timestampStyle = lipgloss.NewStyle().
			Foreground(lipgloss.Color("#888888")).
			Italic(true)

	selectedTimestampStyle = lipgloss.NewStyle().
				Foreground(lipgloss.Color("#CCCCCC")).
				Italic(true)

	detailPanelStyle = lipgloss.NewStyle().
				Border(lipgloss.RoundedBorder()).
				BorderForeground(lipgloss.Color("#7D56F4")).
				Padding(1, 2)

	detailTitleStyle = lipgloss.NewStyle().
				Foreground(lipgloss.Color("#7D56F4")).
				Bold(true).
				Underline(true)

	detailLabelStyle = lipgloss.NewStyle().
				Foreground(lipgloss.Color("#888888")).
				Bold(true)

	detailValueStyle = lipgloss.NewStyle().
				Foreground(lipgloss.Color("#FAFAFA"))

	codeBlockStyle = lipgloss.NewStyle().
			Background(lipgloss.Color("#1a1a1a")).
			Foreground(lipgloss.Color("#00FF00")).
			Padding(1, 1).
			MarginTop(1)
)

type HumanData struct {
	Email               string
	DisplayName         string
	JoinedAt            time.Time
	KeypadConfiguration string
	HasProSubscription  bool
}

type model struct {
	humans             []*HumanData
	cursor             int
	selectedHumanEmail string
	viewport           viewport.Model
	detailViewport     viewport.Model
	ready              bool
	terminalWidth      int
	terminalHeight     int
	focusOnDetail      bool // true = detail panel has focus, false = list has focus
	revenue            string
}

func initialModel(humans []*HumanData) model {
	return model{
		humans:  humans,
		cursor:  0,
		revenue: getRevenue(),
	}
}

func (m model) Init() tea.Cmd {
	// Just return `nil`, which means "no I/O right now, please."
	return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.WindowSizeMsg:
		m.terminalWidth = msg.Width
		m.terminalHeight = msg.Height

		headerHeight := 3 // title + status + separator
		footerHeight := 2 // help text
		verticalMarginHeight := headerHeight + footerHeight

		// Split width: 50% for list, 50% for details
		listWidth := msg.Width / 2
		detailWidth := msg.Width - listWidth - 3 // -3 for padding/border

		if !m.ready {
			m.viewport = viewport.New(listWidth, msg.Height-verticalMarginHeight)
			m.viewport.YPosition = headerHeight
			m.viewport.SetContent(m.renderList())

			m.detailViewport = viewport.New(detailWidth, msg.Height-verticalMarginHeight)
			m.detailViewport.YPosition = headerHeight
			m.detailViewport.SetContent(m.renderDetails())

			m.ready = true
		} else {
			m.viewport.Width = listWidth
			m.viewport.Height = msg.Height - verticalMarginHeight

			m.detailViewport.Width = detailWidth
			m.detailViewport.Height = msg.Height - verticalMarginHeight
		}

	case tea.KeyMsg:
		switch msg.String() {
		case "ctrl+c", "q":
			return m, tea.Quit

		case "ctrl+u":
			// Scroll detail panel up only
			m.detailViewport.LineUp(3)
			return m, nil

		case "ctrl+d":
			// Scroll detail panel down only
			m.detailViewport.LineDown(3)
			return m, nil

		case "up", "k":
			if m.cursor > 0 {
				m.cursor--
				m.viewport.SetContent(m.renderList())
				m.detailViewport.SetContent(m.renderDetails())
				m.detailViewport.GotoTop() // Reset detail scroll position

				// Auto-scroll left viewport to keep cursor visible
				if m.cursor < m.viewport.YOffset {
					m.viewport.YOffset = m.cursor
				}
			}

		case "r":
			m.humans = getHumansData()
			m.cursor = 0
			m.viewport.SetContent(m.renderList())
			m.detailViewport.SetContent(m.renderDetails())
			m.detailViewport.GotoTop()

		case "down", "j":
			if m.cursor < len(m.humans)-1 {
				m.cursor++
				m.viewport.SetContent(m.renderList())
				m.detailViewport.SetContent(m.renderDetails())
				m.detailViewport.GotoTop() // Reset detail scroll position

				// Auto-scroll left viewport to keep cursor visible
				if m.cursor >= m.viewport.YOffset+m.viewport.Height {
					m.viewport.YOffset = m.cursor - m.viewport.Height + 1
				}
			}
		}
	}

	return m, nil
}

func (m model) renderList() string {
	var b strings.Builder
	for i, human := range m.humans {
		email := human.Email

		// Get relative time ago
		timeAgo := timediff.TimeDiff(human.JoinedAt)

		if m.cursor == i {
			// Current selection - highlighted
			cursor := cursorStyle.Render("▸")
			item := selectedItemStyle.Render(email)
			timestamp := selectedTimestampStyle.Render(fmt.Sprintf("(%s)", timeAgo))
			b.WriteString(fmt.Sprintf("%s %s %s\n", cursor, item, timestamp))
		} else {
			// Normal item
			cursor := dimItemStyle.Render(" ")
			item := normalItemStyle.Render(email)
			timestamp := timestampStyle.Render(fmt.Sprintf("(%s)", timeAgo))
			b.WriteString(fmt.Sprintf("%s %s %s\n", cursor, item, timestamp))
		}
	}
	return b.String()
}

func (m model) renderDetails() string {
	if len(m.humans) == 0 || m.cursor >= len(m.humans) {
		return ""
	}

	human := m.humans[m.cursor]
	var b strings.Builder

	// Title
	b.WriteString(detailTitleStyle.Render("Human Details") + "\n\n")

	// Email
	b.WriteString(detailLabelStyle.Render("Email: "))
	b.WriteString(detailValueStyle.Render(human.Email) + "\n\n")

	// Display Name
	if human.DisplayName != "" {
		b.WriteString(detailLabelStyle.Render("Display Name: "))
		b.WriteString(detailValueStyle.Render(human.DisplayName) + "\n\n")
	}

	// Joined At
	b.WriteString(detailLabelStyle.Render("Joined: "))
	timeAgo := timediff.TimeDiff(human.JoinedAt)
	joinedFormatted := human.JoinedAt.Format("Jan 2, 2006 at 3:04 PM")
	b.WriteString(detailValueStyle.Render(fmt.Sprintf("%s (%s)", joinedFormatted, timeAgo)) + "\n\n")

	// Pro Subscription Status
	b.WriteString(detailLabelStyle.Render("Pro Subscription: "))
	if human.HasProSubscription {
		proStyle := lipgloss.NewStyle().
			Foreground(lipgloss.Color("#00FF00")).
			Bold(true)
		b.WriteString(proStyle.Render("✓ Active") + "\n\n")
	} else {
		b.WriteString(dimItemStyle.Render("✗ None") + "\n\n")
	}

	// Keypad Configuration
	if human.KeypadConfiguration != "" {
		b.WriteString(detailLabelStyle.Render("Keypad Configuration:") + "\n")
		b.WriteString(codeBlockStyle.Render(human.KeypadConfiguration))
	} else {
		b.WriteString(detailLabelStyle.Render("Keypad Configuration: "))
		b.WriteString(dimItemStyle.Render("(none)"))
	}

	return b.String()
}

func (m model) View() string {
	if !m.ready {
		return "\n  Initializing..."
	}

	// Header with main data
	sevenDaysAgo := time.Now().AddDate(0, 0, -7)
	pastSevenDaysSignupsCount := 0
	for _, human := range m.humans {
		if human.JoinedAt.After(sevenDaysAgo) {
			pastSevenDaysSignupsCount++
		}
	}
	header := titleStyle.Render(fmt.Sprintf("👥 Humans | %d total | %d in the past 7 days | Revenue: %s", len(m.humans), pastSevenDaysSignupsCount, m.revenue))

	// Status bar showing current position
	statusBar := statusStyle.Render(fmt.Sprintf("  %d/%d", m.cursor+1, len(m.humans)))

	// Help text
	help := helpStyle.Render("↑/k up • ↓/j down • ctrl+u/d scroll detail • r refresh • q quit")

	// Create two-column layout
	leftPanel := m.viewport.View()
	rightPanel := m.detailViewport.View()

	// Combine panels side by side
	mainContent := lipgloss.JoinHorizontal(
		lipgloss.Top,
		leftPanel,
		"  ", // spacing
		rightPanel,
	)

	return fmt.Sprintf("%s%s\n%s\n%s", header, statusBar, mainContent, help)
}

func unaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	md := metadata.Pairs("authorization", "talksik")
	ctx = metadata.NewOutgoingContext(ctx, md)
	return invoker(ctx, method, req, reply, cc, opts...)
}

func main() {
	logrus.Infof("hello world")

	humansData := getHumansData()
	p := tea.NewProgram(initialModel(humansData))
	if _, err := p.Run(); err != nil {
		fmt.Printf("Alas, there's been an error: %v", err)
		os.Exit(1)
	}
}

func getHumansData() []*HumanData {
	heliosAddr := "helios.flowy.live:443"
	creds := credentials.NewTLS(&tls.Config{
		InsecureSkipVerify: true,
	})
	heliosService, err := grpc.NewClient(heliosAddr, grpc.WithTransportCredentials(creds), grpc.WithUnaryInterceptor(unaryInterceptor))
	if err != nil {
		logrus.Fatalf("connection to aero server invalid: %v", err)
	}
	defer heliosService.Close()

	humanService := pbhuman.NewHumanServiceClient(heliosService)
	keypadService := pbkeypad.NewKeypadServiceClient(heliosService)
	humansResponse, err := humanService.ListHumans(context.Background(), &pbhuman.ListHumansRequest{})
	if err != nil {
		logrus.Fatalf("unable to fetch humans: %v", err)
		os.Exit(1)
	}

	keypadsResponse, err := keypadService.ListKeypads(context.Background(), &pbkeypad.ListKeypadsRequest{})
	if err != nil {
		logrus.Fatalf("unable to fetch keypads: %v", err)
		os.Exit(1)
	}

	humansData := []*HumanData{}
	for _, human := range humansResponse.GetHumans() {
		keypadConfiguration := ""
		for _, keypad := range keypadsResponse.GetHumanKeypads() {
			if keypad.GetHumanId() == human.GetId() {
				keypadConfiguration = keypad.GetYamlConfig()
			}
		}

		// Check if customer has pro subscription
		hasPro := hasProSubscription(human.GetEmail())

		humansData = append(humansData, &HumanData{
			Email:               human.GetEmail(),
			DisplayName:         human.GetDisplayName(),
			JoinedAt:            human.GetJoinedAt().AsTime(),
			KeypadConfiguration: keypadConfiguration,
			HasProSubscription:  hasPro,
		})
	}

	return humansData
}
