package main

import (
	"errors"
	"log"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

func getIndex(c *gin.Context) {
	if getSessionUser(c) != nil {
		c.Redirect(302, "/enter")
		return
	}
	getLogin(c)
}

func getLogin(c *gin.Context) {
	tm.Render(c, "login", gin.H{})
}

func postLogin(c *gin.Context) {
	email := strings.ToLower(strings.TrimSpace(c.PostForm("email")))
	if email == "" {
		c.Redirect(302, "/login")
		return
	}

	// ensure user exists
	var u User
	tx := db.Where("email = ?", email).First(&u)
	if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
		u = User{Email: email, Username: defaultUsername()}
		if err := ensureUniqueUsernameAndSlug(&u); err != nil {
			log.Println(err)
		}
		if err := db.Create(&u).Error; err != nil {
			log.Println("create user:", err)
		}
	}

	// create token valid 30 days
	token := mustRandToken(24)
	lt := LoginToken{Token: token, Email: email, ExpiresAt: now().Add(30 * 24 * time.Hour)}
	if err := db.Create(&lt).Error; err != nil {
		log.Println("create token:", err)
	}

	link := strings.TrimRight(baseURL, "/") + "/magic?token=" + token
	log.Printf("[MAGIC LINK] %s for %s (valid until %s)\n", link, email, lt.ExpiresAt.Format(time.RFC3339))

	tm.Render(c, "sent", gin.H{"Email": email})
}

func getMagic(c *gin.Context) {
	token := c.Query("token")
	if token == "" {
		c.Redirect(302, "/login")
		return
	}
	var lt LoginToken
	if err := db.Where("token = ? AND expires_at > ?", token, now()).First(&lt).Error; err != nil {
		c.String(400, "Invalid or expired token")
		return
	}
	// find or create user by email again (in case)
	u, err := userByEmail(lt.Email)
	if err != nil {
		c.String(500, "user lookup error")
		return
	}

	// create session
	sessTok := mustRandToken(24)
	if err := db.Create(&Session{Token: sessTok, UserID: u.ID}).Error; err != nil {
		c.String(500, "session error")
		return
	}
	setSessionCookie(c, sessTok)

	c.Redirect(302, "/enter")
}

func getEnter(c *gin.Context) {
	var table *Table
	if slug := c.Param("tslug"); slug != "" {
		var t Table
		if err := db.Where("slug = ?", slug).First(&t).Error; err == nil {
			table = &t
		}
	}
	post := "/enter"
	if table != nil {
		post = "/t/" + table.Slug + "/enter"
	}
	tm.Render(c, "enter", gin.H{"PostAction": post, "Table": table})
}

func postEnter(c *gin.Context) {
	current := getSessionUser(c)
	if current == nil {
		c.Redirect(302, "/login")
		return
	}

	a1h := c.PostForm("a1")
	a2h := c.PostForm("a2")
	b1h := c.PostForm("b1")
	b2h := c.PostForm("b2")
	scoreA := atoiSafe(c.PostForm("scoreA"))
	scoreB := atoiSafe(c.PostForm("scoreB"))

	// Resolve players (default A1 to current if empty)
	if strings.TrimSpace(a1h) == "" {
		a1h = current.Username
	}
	a1, _ := findOrCreateUserByHandle(a1h)
	var a2, b1, b2 *User
	if a2h != "" {
		a2, _ = findOrCreateUserByHandle(a2h)
	}
	if b1h != "" {
		b1, _ = findOrCreateUserByHandle(b1h)
	}
	if b2h != "" {
		b2, _ = findOrCreateUserByHandle(b2h)
	}

	var tableID *uint
	if slug := c.Param("tslug"); slug != "" {
		var t Table
		if err := db.Where("slug = ?", slug).First(&t).Error; err == nil {
			tableID = &t.ID
		}
	}

	g := Game{ScoreA: scoreA, ScoreB: scoreB, TableID: tableID}
	if err := db.Create(&g).Error; err != nil {
		c.String(500, "game create error")
		return
	}

	players := []struct {
		U *User
		S string
	}{{a1, "A"}}
	if a2 != nil {
		players = append(players, struct {
			U *User
			S string
		}{a2, "A"})
	}
	if b1 != nil {
		players = append(players, struct {
			U *User
			S string
		}{b1, "B"})
	}
	if b2 != nil {
		players = append(players, struct {
			U *User
			S string
		}{b2, "B"})
	}
	for _, p := range players {
		if p.U != nil {
			db.Create(&GamePlayer{GameID: g.ID, UserID: p.U.ID, Side: p.S})
		}
	}

	c.Redirect(302, "/history")
}
func getHistory(c *gin.Context) {
	// Load recent games with players
	var games []Game
	db.Order("created_at desc").Limit(100).Preload("Table").Find(&games)

	type GRow struct {
		Game
		PlayersA []User
		PlayersB []User
	}
	var rows []GRow
	for _, g := range games {
		var gps []GamePlayer
		db.Preload("User").Where("game_id = ?", g.ID).Find(&gps)
		var a, b []User
		for _, gp := range gps {
			if gp.Side == "A" {
				a = append(a, gp.User)
			} else {
				b = append(b, gp.User)
			}
		}
		rows = append(rows, GRow{Game: g, PlayersA: a, PlayersB: b})
	}
	tm.Render(c, "history", gin.H{"Games": rows})
}

func getLeaderboard(c *gin.Context) {
	// Simple metric: wins = games where player's side has higher score
	type Row struct {
		Username, Slug string
		Wins, Games    int
	}

	// Load last 1000 games for performance (simple MVP)
	var games []Game
	db.Order("created_at desc").Limit(1000).Find(&games)

	// Build map of gameID->winnerSide
	winner := map[uint]string{}
	for _, g := range games {
		side := ""
		if g.ScoreA > g.ScoreB {
			side = "A"
		} else if g.ScoreB > g.ScoreA {
			side = "B"
		}
		winner[g.ID] = side
	}

	// Count per user
	type Cnt struct{ Wins, Games int }
	counts := map[uint]*Cnt{}
	users := map[uint]User{}

	for _, g := range games {
		var gps []GamePlayer
		db.Preload("User").Where("game_id = ?", g.ID).Find(&gps)
		for _, gp := range gps {
			if counts[gp.UserID] == nil {
				counts[gp.UserID] = &Cnt{}
				users[gp.UserID] = gp.User
			}
			counts[gp.UserID].Games++
			if winner[g.ID] != "" && winner[g.ID] == gp.Side {
				counts[gp.UserID].Wins++
			}
		}
	}

	rows := []Row{}
	for uid, cnt := range counts {
		u := users[uid]
		rows = append(rows, Row{Username: u.Username, Slug: u.Slug, Wins: cnt.Wins, Games: cnt.Games})
	}

	// Sort by wins desc, then games desc, then username
	// Simple insertion sort to avoid extra imports
	for i := 1; i < len(rows); i++ {
		j := i
		for j > 0 && (rows[j-1].Wins < rows[j].Wins || (rows[j-1].Wins == rows[j].Wins && (rows[j-1].Games < rows[j].Games || (rows[j-1].Games == rows[j].Games && rows[j-1].Username > rows[j].Username)))) {
			rows[j-1], rows[j] = rows[j], rows[j-1]
			j--
		}
	}

	if len(rows) > 100 {
		rows = rows[:100]
	}

	tm.Render(c, "leaderboard", gin.H{"Rows": rows})
}

func getUserView(c *gin.Context) {
	slug := c.Param("slug")
	u, err := userBySlug(slug)
	if err != nil {
		c.String(404, "user not found")
		return
	}

	// Compute stats
	type Stats struct{ Games, Wins, Losses int }
	st := Stats{}
	var gps []GamePlayer
	db.Where("user_id = ?", u.ID).Find(&gps)
	gameMap := map[uint]string{}
	for _, gp := range gps {
		gameMap[gp.GameID] = gp.Side
	}

	if len(gameMap) > 0 {
		var games []Game
		ids := make([]uint, 0, len(gameMap))
		for gid := range gameMap {
			ids = append(ids, gid)
		}
		db.Find(&games, ids)
		for _, g := range games {
			st.Games++
			if g.ScoreA == g.ScoreB {
				continue
			}
			if g.ScoreA > g.ScoreB && gameMap[g.ID] == "A" {
				st.Wins++
			} else if g.ScoreB > g.ScoreA && gameMap[g.ID] == "B" {
				st.Wins++
			} else {
				st.Losses++
			}
		}
	}

	own := false
	if cu := getSessionUser(c); cu != nil && cu.ID == u.ID {
		own = true
	}

	tm.Render(c, "user", gin.H{"Viewed": u, "Stats": st, "Own": own})
}

func getMe(c *gin.Context) {
	cu := getSessionUser(c)
	if cu == nil {
		c.Redirect(302, "/login")
		return
	}
	tm.Render(c, "user", gin.H{"Viewed": cu, "Stats": struct{ Games, Wins, Losses int }{0, 0, 0}, "Own": true})
}

func postMe(c *gin.Context) {
	cu := getSessionUser(c)
	if cu == nil {
		c.Redirect(302, "/login")
		return
	}
	newU := strings.TrimSpace(c.PostForm("username"))
	if newU == "" {
		c.Redirect(302, "/me")
		return
	}
	cu.Username = newU
	cu.Slug = slugify(newU)
	if err := ensureUniqueUsernameAndSlug(cu); err != nil {
		log.Println("unique username error:", err)
	}
	if err := db.Save(cu).Error; err != nil {
		log.Println("save user:", err)
	}
	c.Redirect(302, "/u/"+cu.Slug)
}
