package api

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

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

	"forum-backend/internal/auth"
)

type post struct {
	ID              uuid.UUID `json:"id"`
	ThreadID        uuid.UUID `json:"thread_id"`
	AuthorID        uuid.UUID `json:"author_id"`
	AuthorUsername  string    `json:"author_username"`
	AuthorPostCount int32     `json:"author_post_count"`
	AuthorCreatedAt time.Time `json:"author_created_at"`
	Content         string    `json:"content"`
	CreatedAt       time.Time `json:"created_at"`
	UpdatedAt       time.Time `json:"updated_at"`
}

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

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

	_, _ = app.db.Exec(r.Context(),
		"UPDATE threads SET view_count = view_count + 1 WHERE id = $1",
		(*[16]byte)(&threadID),
	)

	rows, err := app.db.Query(r.Context(),
		`SELECT p.id, p.thread_id, p.author_id,
		        u.username, u.post_count, u.created_at,
		        p.content, p.created_at, p.updated_at
		 FROM posts p
		 JOIN users u ON u.id = p.author_id
		 WHERE p.thread_id = $1
		 ORDER BY p.created_at ASC
		 LIMIT $2 OFFSET $3`,
		(*[16]byte)(&threadID), perPage, offset,
	)
	if err != nil {
		writeError(w, pgxToAppError(err))
		return
	}
	defer rows.Close()

	posts := []post{}
	for rows.Next() {
		var p post
		if err := rows.Scan(
			(*[16]byte)(&p.ID), (*[16]byte)(&p.ThreadID), (*[16]byte)(&p.AuthorID),
			&p.AuthorUsername, &p.AuthorPostCount, &p.AuthorCreatedAt,
			&p.Content, &p.CreatedAt, &p.UpdatedAt,
		); err != nil {
			writeError(w, pgxToAppError(err))
			return
		}
		posts = append(posts, p)
	}
	if err := rows.Err(); err != nil {
		writeError(w, pgxToAppError(err))
		return
	}

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

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

	u, _ := auth.UserFrom(r)

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

	var isLocked bool
	err = app.db.QueryRow(r.Context(),
		"SELECT is_locked FROM threads WHERE id = $1",
		(*[16]byte)(&threadID),
	).Scan(&isLocked)
	if err != nil {
		writeError(w, pgxToAppError(err))
		return
	}
	if isLocked {
		writeError(w, errForbidden)
		return
	}

	var p post
	err = app.db.QueryRow(r.Context(),
		`INSERT INTO posts (thread_id, author_id, content)
		 VALUES ($1, $2, $3)
		 RETURNING id, thread_id, author_id, content, created_at, updated_at`,
		(*[16]byte)(&threadID), (*[16]byte)(&u.UserID), req.Content,
	).Scan(
		(*[16]byte)(&p.ID), (*[16]byte)(&p.ThreadID), (*[16]byte)(&p.AuthorID),
		&p.Content, &p.CreatedAt, &p.UpdatedAt,
	)
	if err != nil {
		writeError(w, pgxToAppError(err))
		return
	}
	p.AuthorUsername = u.Username

	writeJSON(w, http.StatusCreated, map[string]any{
		"id": p.ID, "thread_id": p.ThreadID, "author_id": p.AuthorID,
		"author_username": p.AuthorUsername, "author_post_count": p.AuthorPostCount,
		"author_created_at": p.AuthorCreatedAt, "content": p.Content,
		"created_at": p.CreatedAt, "updated_at": p.UpdatedAt,
		"served_by": app.config.InstanceID,
	})
}

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

	u, _ := auth.UserFrom(r)

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

	var authorID uuid.UUID
	err = app.db.QueryRow(r.Context(),
		"SELECT author_id FROM posts WHERE id = $1",
		(*[16]byte)(&postID),
	).Scan((*[16]byte)(&authorID))
	if err != nil {
		writeError(w, pgxToAppError(err))
		return
	}
	if authorID != u.UserID {
		writeError(w, errForbidden)
		return
	}

	var p post
	err = app.db.QueryRow(r.Context(),
		`UPDATE posts SET content = $1, updated_at = NOW()
		 WHERE id = $2
		 RETURNING id, thread_id, author_id, content, created_at, updated_at`,
		req.Content, (*[16]byte)(&postID),
	).Scan(
		(*[16]byte)(&p.ID), (*[16]byte)(&p.ThreadID), (*[16]byte)(&p.AuthorID),
		&p.Content, &p.CreatedAt, &p.UpdatedAt,
	)
	if err != nil {
		writeError(w, pgxToAppError(err))
		return
	}
	p.AuthorUsername = u.Username

	writeJSON(w, http.StatusOK, map[string]any{
		"id": p.ID, "thread_id": p.ThreadID, "author_id": p.AuthorID,
		"author_username": p.AuthorUsername, "author_post_count": p.AuthorPostCount,
		"author_created_at": p.AuthorCreatedAt, "content": p.Content,
		"created_at": p.CreatedAt, "updated_at": p.UpdatedAt,
		"served_by": app.config.InstanceID,
	})
}

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

	u, _ := auth.UserFrom(r)

	var authorID uuid.UUID
	err = app.db.QueryRow(r.Context(),
		"SELECT author_id FROM posts WHERE id = $1",
		(*[16]byte)(&postID),
	).Scan((*[16]byte)(&authorID))
	if err != nil {
		writeError(w, pgxToAppError(err))
		return
	}
	if authorID != u.UserID {
		writeError(w, errForbidden)
		return
	}

	if _, err := app.db.Exec(r.Context(),
		"DELETE FROM posts WHERE id = $1", (*[16]byte)(&postID),
	); err != nil {
		writeError(w, pgxToAppError(err))
		return
	}

	w.WriteHeader(http.StatusNoContent)
}
