package routes

import (
	"dk/internal/components"
	"dk/internal/database"
	"dk/internal/helpers"
	"dk/internal/helpers/markdown"
	"dk/internal/models/forum"
	"dk/internal/models/users"
	"fmt"
	"log"
	"strings"
	"time"

	sushi "git.sharkk.net/Sharkk/Sushi"
	"git.sharkk.net/Sharkk/Sushi/auth"
)

type threadData struct {
	ID                int
	Posted            int64
	LastPost          int64
	Author            int
	Parent            int
	Replies           int
	Title             string
	Content           string
	AuthorUsername    string
	AuthorLevel       int
	AuthorClass       string
	LastReplyUsername string
	LastReplyLevel    int
	LastReplyClassID  int
	HasReplies        bool
}

type replyData struct {
	ID             int
	Posted         int64
	Author         int
	Title          string
	Content        string
	AuthorUsername string
	AuthorLevel    int
	AuthorClass    string
}

func (t *threadData) PostedTime() time.Time {
	return time.Unix(t.Posted, 0)
}

func (t *threadData) LastPostTime() time.Time {
	return time.Unix(t.LastPost, 0)
}

func (r *replyData) PostedTime() time.Time {
	return time.Unix(r.Posted, 0)
}

func RegisterForumRoutes(app *sushi.App) {
	authed := app.Group("/forum")
	authed.Use(auth.RequireAuth())
	authed.Get("/", index)
	authed.Get("/new", showNew)
	authed.Post("/new", new)
	authed.Get("/:id", showThread)
	authed.Post("/:id/reply", reply)
}

// Helper function to validate thread exists and is actually a thread (not a reply)
func validateThread(ctx sushi.Ctx, id int) (*forum.Forum, bool) {
	sess := ctx.GetCurrentSession()
	thread, err := forum.Find(id)
	if err != nil {
		sess.SetFlash("error", fmt.Sprintf("Forum thread %d not found", id))
		ctx.Redirect("/forum")
		return nil, false
	}

	if thread.Parent != 0 {
		sess.SetFlash("error", fmt.Sprintf("Forum post %d is not a thread", id))
		ctx.Redirect("/forum")
		return nil, false
	}

	return thread, true
}

// Helper function to get pagination params from request
func getPagination(ctx sushi.Ctx, perPage int) helpers.Pagination {
	page := max(int(ctx.QueryArgs().GetUintOrZero("page")), 1)
	return helpers.Pagination{
		Page:    page,
		PerPage: perPage,
	}
}

func index(ctx sushi.Ctx) {
	pagination := getPagination(ctx, 30)

	// Get threads with user data
	var threads []*threadData
	query := `
		SELECT f.id, f.posted, f.last_post, f.author, f.parent, f.replies, f.title, f.content,
		       COALESCE(u.username, '[Deleted]') as author_username, 
		       COALESCE(u.level, 1) as author_level,
		       COALESCE(c.name, 'Unknown') as author_class
		FROM forum f
		LEFT JOIN users u ON f.author = u.id
		LEFT JOIN classes c ON u.class_id = c.id
		WHERE f.parent = 0 
		ORDER BY f.last_post DESC, f.id DESC 
		LIMIT %d OFFSET %d`

	err := database.Select(&threads, query, pagination.PerPage, pagination.Offset())
	if err != nil {
		log.Printf("Error fetching forum threads: %v", err)
		threads = make([]*threadData, 0)
	}

	// Get last reply info for each thread
	for i := range threads {
		type ReplyCount struct{ Count int }
		var replyCount ReplyCount
		database.Get(&replyCount, "SELECT COUNT(*) as count FROM forum WHERE parent = %d", threads[i].ID)

		if replyCount.Count > 0 {
			type LastReply struct{ Username string }
			var lastReply LastReply
			err := database.Get(&lastReply, `
				SELECT COALESCE(u.username, '[Deleted]') as username
				FROM forum f 
				LEFT JOIN users u ON f.author = u.id 
				WHERE f.parent = %d 
				ORDER BY f.posted DESC 
				LIMIT 1`, threads[i].ID)

			if err == nil {
				threads[i].LastReplyUsername = lastReply.Username
				threads[i].HasReplies = true
			} else {
				log.Printf("Error getting last reply author for thread %d: %v", threads[i].ID, err)
			}
		}
	}

	// Get total count for pagination
	type CountResult struct{ Count int }
	var paginationResult CountResult
	database.Get(&paginationResult, "SELECT COUNT(*) as count FROM forum WHERE parent = 0")
	pagination.Total = paginationResult.Count

	components.RenderPage(ctx, "Forum", "forum/index.html", map[string]any{
		"threads":     threads,
		"currentPage": pagination.Page,
		"totalPages":  pagination.TotalPages(),
		"hasNext":     pagination.HasNext(),
		"hasPrev":     pagination.HasPrev(),
	})
}

func showNew(ctx sushi.Ctx) {
	components.RenderPage(ctx, "New Forum Thread", "forum/new.html", map[string]any{})
}

func new(ctx sushi.Ctx) {
	sess := ctx.GetCurrentSession()

	title := strings.TrimSpace(ctx.Form("title").String())
	content := strings.TrimSpace(ctx.Form("content").String())

	if title == "" {
		sess.SetFlash("error", "Thread title cannot be empty")
		ctx.Redirect("/forum/new")
		return
	}

	if content == "" {
		sess.SetFlash("error", "Thread content cannot be empty")
		ctx.Redirect("/forum/new")
		return
	}

	user := ctx.GetCurrentUser().(*users.User)

	thread := forum.New()
	thread.Author = user.ID
	thread.Title = title
	thread.Content = content

	if err := thread.Insert(); err != nil {
		log.Printf("Error creating thread: %v", err)
		sess.SetFlash("error", "Failed to create thread")
		ctx.Redirect("/forum/new")
		return
	}

	ctx.Redirect(fmt.Sprintf("/forum/%d", thread.ID))
}

func showThread(ctx sushi.Ctx) {
	id := ctx.Param("id").Int()
	thread, ok := validateThread(ctx, id)
	if !ok {
		return
	}

	pagination := getPagination(ctx, 10)
	pagination.Total = thread.Replies

	// Get thread with author data
	var threadData threadData
	err := database.Get(&threadData, `
		SELECT f.id, f.posted, f.last_post, f.author, f.parent, f.replies, f.title, f.content,
		       COALESCE(u.username, '[Deleted]') as author_username, 
		       COALESCE(u.level, 1) as author_level,
		       COALESCE(c.name, 'Unknown') as author_class
		FROM forum f
		LEFT JOIN users u ON f.author = u.id
		LEFT JOIN classes c ON u.class_id = c.id
		WHERE f.id = %d`, id)

	if err != nil {
		log.Printf("Error loading thread %d: %v", id, err)
		sess := ctx.GetCurrentSession()
		sess.SetFlash("error", "Failed to load thread")
		ctx.Redirect("/forum")
		return
	}

	// Get replies with user data
	var replies []*replyData
	err = database.Select(&replies, `
		SELECT f.id, f.posted, f.author, f.title, f.content,
		       COALESCE(u.username, '[Deleted]') as author_username, 
		       COALESCE(u.level, 1) as author_level,
		       COALESCE(c.name, 'Unknown') as author_class
		FROM forum f
		LEFT JOIN users u ON f.author = u.id
		LEFT JOIN classes c ON u.class_id = c.id
		WHERE f.parent = %d 
		ORDER BY f.posted ASC 
		LIMIT %d OFFSET %d`, id, pagination.PerPage, pagination.Offset())

	if err != nil {
		log.Printf("Error loading replies for thread %d: %v", id, err)
		replies = make([]*replyData, 0)
	}

	components.RenderPage(ctx, threadData.Title, "forum/thread.html", map[string]any{
		"thread":      threadData,
		"replies":     replies,
		"currentPage": pagination.Page,
		"totalPages":  pagination.TotalPages(),
		"hasNext":     pagination.HasNext(),
		"hasPrev":     pagination.HasPrev(),
		"markdown":    markdown.MarkdownToHTML,
	})
}

func reply(ctx sushi.Ctx) {
	sess := ctx.GetCurrentSession()
	id := ctx.Param("id").Int()

	thread, ok := validateThread(ctx, id)
	if !ok {
		return
	}

	content := strings.TrimSpace(ctx.Form("content").String())
	if content == "" {
		sess.SetFlash("error", "Reply content cannot be empty")
		ctx.Redirect(fmt.Sprintf("/forum/%d#reply", id))
		return
	}

	user := ctx.GetCurrentUser().(*users.User)

	reply := forum.New()
	reply.Author = user.ID
	reply.Parent = thread.ID
	reply.Title = "Re: " + thread.Title
	reply.Content = content

	err := database.Transaction(func() error {
		if err := reply.Insert(); err != nil {
			return err
		}

		// Update thread reply count and last post time
		thread.IncrementReplies()
		thread.UpdateLastPost()

		return database.Exec("UPDATE forum SET replies = %d, last_post = %d WHERE id = %d",
			thread.Replies, thread.LastPost, thread.ID)
	})

	if err != nil {
		log.Printf("Error posting reply to thread %d: %v", id, err)
		sess.SetFlash("error", "Failed to post reply")
		ctx.Redirect(fmt.Sprintf("/forum/%d#reply", id))
		return
	}

	// Calculate the last page where the new reply will appear
	const repliesPerPage = 10
	lastPage := max((thread.Replies+repliesPerPage-1)/repliesPerPage, 1)

	ctx.Redirect(fmt.Sprintf("/forum/%d?page=%d", thread.ID, lastPage))
}
