package api

import (
	"encoding/json"
	"net/http"
	"strings"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/google/uuid"

	"forum-backend/internal/auth"
)

type thread struct {
	ID             uuid.UUID `json:"id"`
	CategoryID     uuid.UUID `json:"category_id"`
	AuthorID       uuid.UUID `json:"author_id"`
	AuthorUsername string    `json:"author_username"`
	Title          string    `json:"title"`
	IsPinned       bool      `json:"is_pinned"`
	IsLocked       bool      `json:"is_locked"`
	ReplyCount     int32     `json:"reply_count"`
	ViewCount      int32     `json:"view_count"`
	LastPostAt     time.Time `json:"last_post_at"`
	CreatedAt      time.Time `json:"created_at"`
}

func (app *App) listThreads(w http.ResponseWriter, r *http.Request) {
	categoryID, err := uuid.Parse(chi.URLParam(r, "categoryID"))
	if err != nil {
		writeError(w, errNotFound)
		return
	}

	page, perPage := parsePagination(r)
	offset := (page - 1) * perPage

	rows, err := app.db.Query(r.Context(),
		`SELECT t.id, t.category_id, t.author_id, u.username,
		        t.title, t.is_pinned, t.is_locked, t.reply_count, t.view_count,
		        t.last_post_at, t.created_at
		 FROM threads t
		 JOIN users u ON u.id = t.author_id
		 WHERE t.category_id = $1
		 ORDER BY t.is_pinned DESC, t.last_post_at DESC
		 LIMIT $2 OFFSET $3`,
		(*[16]byte)(&categoryID), perPage, offset,
	)
	if err != nil {
		writeError(w, pgxToAppError(err))
		return
	}
	defer rows.Close()

	threads := []thread{}
	for rows.Next() {
		var t thread
		if err := rows.Scan(
			(*[16]byte)(&t.ID), (*[16]byte)(&t.CategoryID), (*[16]byte)(&t.AuthorID),
			&t.AuthorUsername, &t.Title, &t.IsPinned, &t.IsLocked,
			&t.ReplyCount, &t.ViewCount, &t.LastPostAt, &t.CreatedAt,
		); err != nil {
			writeError(w, pgxToAppError(err))
			return
		}
		threads = append(threads, t)
	}
	if err := rows.Err(); err != nil {
		writeError(w, pgxToAppError(err))
		return
	}

	writeJSON(w, http.StatusOK, map[string]any{
		"threads": threads, "page": page, "per_page": perPage,
		"served_by": app.config.InstanceID,
	})
}

func (app *App) createThread(w http.ResponseWriter, r *http.Request) {
	categoryID, err := uuid.Parse(chi.URLParam(r, "categoryID"))
	if err != nil {
		writeError(w, errNotFound)
		return
	}

	u, _ := auth.UserFrom(r)

	var req struct {
		Title   string `json:"title"`
		Content string `json:"content"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, unprocessable("invalid JSON"))
		return
	}
	req.Title = strings.TrimSpace(req.Title)
	req.Content = strings.TrimSpace(req.Content)
	if req.Title == "" {
		writeError(w, unprocessable("title is required"))
		return
	}
	if req.Content == "" {
		writeError(w, unprocessable("content is required"))
		return
	}

	var exists bool
	_ = app.db.QueryRow(r.Context(),
		"SELECT EXISTS(SELECT 1 FROM categories WHERE id = $1)",
		(*[16]byte)(&categoryID),
	).Scan(&exists)
	if !exists {
		writeError(w, errNotFound)
		return
	}

	tx, err := app.db.Begin(r.Context())
	if err != nil {
		writeError(w, internal(err.Error()))
		return
	}
	defer tx.Rollback(r.Context()) //nolint:errcheck

	var t thread
	err = tx.QueryRow(r.Context(),
		`WITH ins AS (
		     INSERT INTO threads (category_id, author_id, title)
		     VALUES ($1, $2, $3)
		     RETURNING id, category_id, author_id, title, is_pinned, is_locked,
		               reply_count, view_count, last_post_at, created_at
		 )
		 SELECT ins.id, ins.category_id, ins.author_id, $4::text,
		        ins.title, ins.is_pinned, ins.is_locked, ins.reply_count, ins.view_count,
		        ins.last_post_at, ins.created_at
		 FROM ins`,
		(*[16]byte)(&categoryID), (*[16]byte)(&u.UserID), req.Title, u.Username,
	).Scan(
		(*[16]byte)(&t.ID), (*[16]byte)(&t.CategoryID), (*[16]byte)(&t.AuthorID),
		&t.AuthorUsername, &t.Title, &t.IsPinned, &t.IsLocked,
		&t.ReplyCount, &t.ViewCount, &t.LastPostAt, &t.CreatedAt,
	)
	if err != nil {
		writeError(w, pgxToAppError(err))
		return
	}

	if _, err := tx.Exec(r.Context(),
		"INSERT INTO posts (thread_id, author_id, content) VALUES ($1, $2, $3)",
		(*[16]byte)(&t.ID), (*[16]byte)(&u.UserID), req.Content,
	); err != nil {
		writeError(w, pgxToAppError(err))
		return
	}

	if err := tx.Commit(r.Context()); err != nil {
		writeError(w, internal(err.Error()))
		return
	}

	writeJSON(w, http.StatusCreated, map[string]any{
		"id": t.ID, "category_id": t.CategoryID, "author_id": t.AuthorID,
		"author_username": t.AuthorUsername, "title": t.Title,
		"is_pinned": t.IsPinned, "is_locked": t.IsLocked,
		"reply_count": t.ReplyCount, "view_count": t.ViewCount,
		"last_post_at": t.LastPostAt, "created_at": t.CreatedAt,
		"served_by": app.config.InstanceID,
	})
}
