package api

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/lib/pq"

	"github.com/rycroftapparel/workpulse-api/internal/httpapi"
)

type chatPostBody struct {
	Body             string   `json:"body"`
	MentionUserIDs   []uint64 `json:"mentionUserIds"`
	ReplyToMessageID *uint64  `json:"replyToMessageId"`
}

func (s *Server) listChatMessages(c *gin.Context) {
	ctx, cancel := s.ctx(c)
	defer cancel()

	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
	if limit < 1 {
		limit = 1
	}
	if limit > 100 {
		limit = 100
	}
	beforeID, _ := strconv.ParseUint(strings.TrimSpace(c.Query("beforeId")), 10, 64)

	var rows *sql.Rows
	var err error
	if beforeID > 0 {
		rows, err = s.DB.QueryContext(ctx, `
			SELECT m.id, m.user_id, COALESCE(NULLIF(TRIM(u.name), ''), u.email), u.email, u.avatar_url, m.body, m.mention_user_ids, m.created_at,
			       m.reply_to_message_id, COALESCE(NULLIF(TRIM(ru.name), ''), ru.email), rm.body
			FROM chat_messages m
			INNER JOIN users u ON u.id = m.user_id
			LEFT JOIN chat_messages rm ON rm.id = m.reply_to_message_id
			LEFT JOIN users ru ON ru.id = rm.user_id
			WHERE m.id < $1
			ORDER BY m.id DESC
			LIMIT $2`, beforeID, limit)
	} else {
		rows, err = s.DB.QueryContext(ctx, `
			SELECT m.id, m.user_id, COALESCE(NULLIF(TRIM(u.name), ''), u.email), u.email, u.avatar_url, m.body, m.mention_user_ids, m.created_at,
			       m.reply_to_message_id, COALESCE(NULLIF(TRIM(ru.name), ''), ru.email), rm.body
			FROM chat_messages m
			INNER JOIN users u ON u.id = m.user_id
			LEFT JOIN chat_messages rm ON rm.id = m.reply_to_message_id
			LEFT JOIN users ru ON ru.id = rm.user_id
			ORDER BY m.id DESC
			LIMIT $1`, limit)
	}
	if err != nil {
		c.JSON(http.StatusInternalServerError, httpapi.Fail("db", err.Error()))
		return
	}
	defer rows.Close()

	batch := []gin.H{}
	messageIDs := []uint64{}
	for rows.Next() {
		var id, userID uint64
		var authorName, authorEmail, body string
		var authorAvatar sql.NullString
		var mentionIDs pq.Int64Array
		var created time.Time
		var replyToID sql.NullInt64
		var replyAuthor, replyBody sql.NullString
		if err := rows.Scan(&id, &userID, &authorName, &authorEmail, &authorAvatar, &body, &mentionIDs, &created, &replyToID, &replyAuthor, &replyBody); err != nil {
			c.JSON(http.StatusInternalServerError, httpapi.Fail("db", err.Error()))
			return
		}
		mentions := int64SliceToUint64(mentionIDs)
		reply := chatReplyResponse(replyToID, replyAuthor, replyBody)
		messageIDs = append(messageIDs, id)
		batch = append(batch, chatMessageJSON(id, userID, authorName, authorEmail, authorAvatar, body, mentions, created, reply))
	}
	if err := rows.Err(); err != nil {
		c.JSON(http.StatusInternalServerError, httpapi.Fail("db", err.Error()))
		return
	}

	// API returns oldest-first for natural chat scroll.
	for i, j := 0, len(batch)-1; i < j; i, j = i+1, j-1 {
		batch[i], batch[j] = batch[j], batch[i]
	}
	attachments, err := s.chatAttachmentsByMessageIDs(ctx, messageIDs)
	if err != nil {
		c.JSON(http.StatusInternalServerError, httpapi.Fail("db", err.Error()))
		return
	}
	for _, row := range batch {
		id, _ := row["id"].(uint64)
		if atts, ok := attachments[id]; ok {
			row["attachments"] = atts
		}
	}

	c.JSON(http.StatusOK, httpapi.OK(batch))
}

func (s *Server) postChatMessage(c *gin.Context) {
	ctx, cancel := s.ctx(c)
	defer cancel()
	uid := userID(c)

	var req chatPostBody
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", "invalid body"))
		return
	}
	body := strings.TrimSpace(req.Body)
	if body == "" {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", "message body is required"))
		return
	}
	if len(body) > 8000 {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", "message too long"))
		return
	}

	mentionIDs := uniqueUint64(req.MentionUserIDs)
	if len(mentionIDs) > 30 {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", "too many mentions"))
		return
	}
	if err := s.validateActiveUserIDs(ctx, mentionIDs); err != nil {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", err.Error()))
		return
	}
	if err := s.validateReplyMessageID(ctx, req.ReplyToMessageID); err != nil {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", err.Error()))
		return
	}

	var id uint64
	var created time.Time
	err := s.DB.QueryRowContext(ctx, `
		INSERT INTO chat_messages (user_id, body, mention_user_ids, reply_to_message_id)
		VALUES ($1, $2, $3, $4)
		RETURNING id, created_at`,
		uid, body, pq.Array(uint64SliceToInt64(mentionIDs)), nullableReplyID(req.ReplyToMessageID),
	).Scan(&id, &created)
	if err != nil {
		c.JSON(http.StatusInternalServerError, httpapi.Fail("db", err.Error()))
		return
	}

	var authorName, authorEmail string
	var authorAvatar sql.NullString
	_ = s.DB.QueryRowContext(ctx,
		`SELECT COALESCE(NULLIF(TRIM(name), ''), email), email, avatar_url FROM users WHERE id = $1`, uid,
	).Scan(&authorName, &authorEmail, &authorAvatar)

	if err := s.notifyOnChatMentions(ctx, uid, authorName, body, mentionIDs); err != nil {
		// Message is saved; notification failure should not block the client.
		_ = err
	}

	var reply interface{}
	if req.ReplyToMessageID != nil {
		reply = s.loadChatReplyResponse(ctx, *req.ReplyToMessageID)
	}
	row := chatMessageJSON(id, uid, authorName, authorEmail, authorAvatar, body, mentionIDs, created, reply)
	row["attachments"] = []chatAttachmentJSON{}
	s.Hub.BroadcastEvent("chat.message.created", row)
	chatLogDetail := body
	if len(chatLogDetail) > 120 {
		chatLogDetail = chatLogDetail[:117] + "..."
	}
	s.recordUserActivity(c, ctx, uid, "chat.message", "Team chat message", chatLogDetail, gin.H{"messageId": id})
	c.JSON(http.StatusCreated, httpapi.OK(row))
}

func (s *Server) postChatMessageWithAttachments(c *gin.Context) {
	ctx, cancel := s.ctx(c)
	defer cancel()
	uid := userID(c)

	body := strings.TrimSpace(c.PostForm("body"))
	if len(body) > 8000 {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", "message too long"))
		return
	}
	var mentionIDs []uint64
	if raw := strings.TrimSpace(c.PostForm("mentionUserIds")); raw != "" {
		if err := json.Unmarshal([]byte(raw), &mentionIDs); err != nil {
			c.JSON(http.StatusBadRequest, httpapi.Fail("validation", "invalid mentionUserIds"))
			return
		}
	}
	mentionIDs = uniqueUint64(mentionIDs)
	if len(mentionIDs) > 30 {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", "too many mentions"))
		return
	}
	if err := s.validateActiveUserIDs(ctx, mentionIDs); err != nil {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", err.Error()))
		return
	}
	replyToID, err := parseOptionalUint64(c.PostForm("replyToMessageId"))
	if err != nil {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", "invalid replyToMessageId"))
		return
	}
	if err := s.validateReplyMessageID(ctx, replyToID); err != nil {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", err.Error()))
		return
	}

	form, err := c.MultipartForm()
	if err != nil {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", "Pilih file gambar."))
		return
	}
	files := form.File["files"]
	if len(files) == 0 {
		files = form.File["file"]
	}
	if body == "" && len(files) == 0 {
		c.JSON(http.StatusBadRequest, httpapi.Fail("validation", "message body or image is required"))
		return
	}

	var id uint64
	var created time.Time
	err = s.DB.QueryRowContext(ctx, `
		INSERT INTO chat_messages (user_id, body, mention_user_ids, reply_to_message_id)
		VALUES ($1, $2, $3, $4)
		RETURNING id, created_at`,
		uid, body, pq.Array(uint64SliceToInt64(mentionIDs)), nullableReplyID(replyToID),
	).Scan(&id, &created)
	if err != nil {
		c.JSON(http.StatusInternalServerError, httpapi.Fail("db", err.Error()))
		return
	}

	attachments, ok := s.saveChatAttachments(c, id, files)
	if !ok {
		_, _ = s.DB.ExecContext(ctx, `DELETE FROM chat_messages WHERE id = $1`, id)
		return
	}

	var authorName, authorEmail string
	var authorAvatar sql.NullString
	_ = s.DB.QueryRowContext(ctx,
		`SELECT COALESCE(NULLIF(TRIM(name), ''), email), email, avatar_url FROM users WHERE id = $1`, uid,
	).Scan(&authorName, &authorEmail, &authorAvatar)

	preview := body
	if preview == "" && len(attachments) > 0 {
		preview = "mengirim gambar"
	}
	if err := s.notifyOnChatMentions(ctx, uid, authorName, preview, mentionIDs); err != nil {
		_ = err
	}

	var reply interface{}
	if replyToID != nil {
		reply = s.loadChatReplyResponse(ctx, *replyToID)
	}
	row := chatMessageJSON(id, uid, authorName, authorEmail, authorAvatar, body, mentionIDs, created, reply)
	row["attachments"] = attachments
	s.Hub.BroadcastEvent("chat.message.created", row)
	chatLogDetail := preview
	if len(chatLogDetail) > 120 {
		chatLogDetail = chatLogDetail[:117] + "..."
	}
	s.recordUserActivity(c, ctx, uid, "chat.message", "Team chat message", chatLogDetail, gin.H{"messageId": id, "attachments": len(attachments)})
	c.JSON(http.StatusCreated, httpapi.OK(row))
}

func (s *Server) listChatMentionUsers(c *gin.Context) {
	ctx, cancel := s.ctx(c)
	defer cancel()

	q := strings.TrimSpace(c.Query("q"))
	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
	if limit < 1 {
		limit = 1
	}
	if limit > 50 {
		limit = 50
	}

	var rows *sql.Rows
	var err error
	divisionSubquery := `(
			SELECT COALESCE(NULLIF(TRIM(od.name), ''), NULLIF(TRIM(od.code), ''), '')
			FROM org_division_members m
			INNER JOIN org_divisions od ON od.id = m.division_id AND od.is_active = true
			WHERE m.user_id = u.id
			ORDER BY od.sort_order ASC, od.name ASC
			LIMIT 1
		)`
	if q == "" {
		rows, err = s.DB.QueryContext(ctx, `
			SELECT u.id, u.email, COALESCE(NULLIF(TRIM(u.name), ''), u.email),
			       COALESCE(`+divisionSubquery+`, '')
			FROM users u
			WHERE u.is_active = true
			ORDER BY u.name ASC, u.email ASC
			LIMIT $1`, limit)
	} else {
		pat := "%" + q + "%"
		rows, err = s.DB.QueryContext(ctx, `
			SELECT u.id, u.email, COALESCE(NULLIF(TRIM(u.name), ''), u.email),
			       COALESCE(`+divisionSubquery+`, '')
			FROM users u
			WHERE u.is_active = true AND (u.name ILIKE $1 OR u.email ILIKE $1)
			ORDER BY u.name ASC, u.email ASC
			LIMIT $2`, pat, limit)
	}
	if err != nil {
		c.JSON(http.StatusInternalServerError, httpapi.Fail("db", err.Error()))
		return
	}
	defer rows.Close()

	out := []gin.H{}
	for rows.Next() {
		var id uint64
		var email, name, division string
		if err := rows.Scan(&id, &email, &name, &division); err != nil {
			c.JSON(http.StatusInternalServerError, httpapi.Fail("db", err.Error()))
			return
		}
		out = append(out, gin.H{
			"id":       id,
			"email":    email,
			"name":     name,
			"division": division,
		})
	}
	if err := rows.Err(); err != nil {
		c.JSON(http.StatusInternalServerError, httpapi.Fail("db", err.Error()))
		return
	}
	c.JSON(http.StatusOK, httpapi.OK(out))
}

func chatMessageJSON(id, userID uint64, authorName, authorEmail string, authorAvatar sql.NullString, body string, mentionIDs []uint64, created time.Time, reply interface{}) gin.H {
	row := gin.H{
		"id":             id,
		"userId":         userID,
		"authorName":     authorName,
		"authorEmail":    authorEmail,
		"body":           body,
		"mentionUserIds": mentionIDs,
		"createdAt":      created,
		"attachments":    []chatAttachmentJSON{},
		"replyTo":        reply,
	}
	if authorAvatar.Valid && strings.TrimSpace(authorAvatar.String) != "" {
		row["authorAvatarUrl"] = strings.TrimSpace(authorAvatar.String)
	}
	return row
}

func chatReplyResponse(id sql.NullInt64, author, body sql.NullString) interface{} {
	if !id.Valid {
		return nil
	}
	preview := strings.TrimSpace(body.String)
	if preview == "" {
		preview = "Gambar"
	}
	if len(preview) > 180 {
		preview = preview[:177] + "..."
	}
	return gin.H{
		"id":         uint64(id.Int64),
		"authorName": strings.TrimSpace(author.String),
		"body":       preview,
	}
}

func (s *Server) loadChatReplyResponse(ctx context.Context, id uint64) interface{} {
	var replyID sql.NullInt64
	var author, body sql.NullString
	err := s.DB.QueryRowContext(ctx, `
		SELECT m.id, COALESCE(NULLIF(TRIM(u.name), ''), u.email), m.body
		FROM chat_messages m
		INNER JOIN users u ON u.id = m.user_id
		WHERE m.id = $1`,
		id,
	).Scan(&replyID, &author, &body)
	if err != nil {
		return nil
	}
	return chatReplyResponse(replyID, author, body)
}

func (s *Server) validateReplyMessageID(ctx context.Context, id *uint64) error {
	if id == nil || *id == 0 {
		return nil
	}
	var exists bool
	if err := s.DB.QueryRowContext(ctx, `SELECT EXISTS (SELECT 1 FROM chat_messages WHERE id = $1)`, *id).Scan(&exists); err != nil {
		return fmt.Errorf("could not validate reply message")
	}
	if !exists {
		return fmt.Errorf("reply message not found")
	}
	return nil
}

func parseOptionalUint64(raw string) (*uint64, error) {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return nil, nil
	}
	v, err := strconv.ParseUint(raw, 10, 64)
	if err != nil || v == 0 {
		return nil, fmt.Errorf("invalid id")
	}
	return &v, nil
}

func nullableReplyID(id *uint64) interface{} {
	if id == nil || *id == 0 {
		return nil
	}
	return *id
}

func (s *Server) validateActiveUserIDs(ctx context.Context, ids []uint64) error {
	if len(ids) == 0 {
		return nil
	}
	rows, err := s.DB.QueryContext(ctx, `SELECT id FROM users WHERE is_active = true AND id = ANY($1)`, pq.Array(uint64SliceToInt64(ids)))
	if err != nil {
		return fmt.Errorf("could not validate mentions")
	}
	defer rows.Close()
	found := map[uint64]struct{}{}
	for rows.Next() {
		var id uint64
		if err := rows.Scan(&id); err != nil {
			return fmt.Errorf("could not validate mentions")
		}
		found[id] = struct{}{}
	}
	if err := rows.Err(); err != nil {
		return fmt.Errorf("could not validate mentions")
	}
	for _, id := range ids {
		if _, ok := found[id]; !ok {
			return fmt.Errorf("invalid mention user id %d", id)
		}
	}
	return nil
}

func uniqueUint64(in []uint64) []uint64 {
	if len(in) == 0 {
		return []uint64{}
	}
	seen := make(map[uint64]struct{}, len(in))
	out := make([]uint64, 0, len(in))
	for _, v := range in {
		if v == 0 {
			continue
		}
		if _, ok := seen[v]; ok {
			continue
		}
		seen[v] = struct{}{}
		out = append(out, v)
	}
	return out
}

func uint64SliceToInt64(in []uint64) []int64 {
	if len(in) == 0 {
		return []int64{}
	}
	out := make([]int64, len(in))
	for i, v := range in {
		out[i] = int64(v)
	}
	return out
}

func int64SliceToUint64(in pq.Int64Array) []uint64 {
	if len(in) == 0 {
		return []uint64{}
	}
	out := make([]uint64, 0, len(in))
	for _, v := range in {
		if v > 0 {
			out = append(out, uint64(v))
		}
	}
	return out
}

// deleteAllChatMessages removes the entire workspace team chat (superadmin only).
func (s *Server) deleteAllChatMessages(c *gin.Context) {
	if roleFromContext(c) != "superadmin" {
		c.JSON(http.StatusForbidden, httpapi.Fail("forbidden", "superadmin role required"))
		return
	}
	ctx, cancel := s.ctx(c)
	defer cancel()

	var deleted int64
	if err := s.DB.QueryRowContext(ctx, `WITH d AS (DELETE FROM chat_messages RETURNING id) SELECT COUNT(*) FROM d`).Scan(&deleted); err != nil {
		c.JSON(http.StatusInternalServerError, httpapi.Fail("db", err.Error()))
		return
	}

	chatRoot := filepath.Join(s.Cfg.UploadDir, "chat")
	_ = os.RemoveAll(chatRoot)
	_ = os.MkdirAll(chatRoot, 0o755)

	uid := userID(c)
	s.recordUserActivity(c, ctx, uid, "chat.cleared", "Team chat cleared", fmt.Sprintf("%d messages removed", deleted), nil)
	s.Hub.BroadcastEvent("chat.cleared", gin.H{"deleted": deleted})

	c.JSON(http.StatusOK, httpapi.OK(gin.H{"deleted": deleted}))
}
