package handlers

import (
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
)

// ListNeighborhoods returns neighborhood seeds with group and activity metadata.
func (h *AdminHandler) ListNeighborhoods(c *gin.Context) {
	ctx := c.Request.Context()

	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
	offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
	search := strings.TrimSpace(c.Query("search"))
	zip := strings.TrimSpace(c.Query("zip"))
	sortBy := strings.TrimSpace(c.DefaultQuery("sort", "name"))
	order := strings.ToLower(strings.TrimSpace(c.DefaultQuery("order", "asc")))

	if limit <= 0 {
		limit = 50
	}
	if limit > 200 {
		limit = 200
	}
	if order != "desc" {
		order = "asc"
	}

	sortColumn := "ns.name"
	switch sortBy {
	case "zip":
		sortColumn = "ns.zip_code"
	case "members":
		sortColumn = "g.member_count"
	case "created":
		sortColumn = "ns.created_at"
	}

	base := `
		FROM neighborhood_seeds ns
		LEFT JOIN groups g ON g.id = ns.group_id
		WHERE 1=1
	`
	args := []any{}
	argIdx := 1

	if search != "" {
		base += fmt.Sprintf(" AND (ns.name ILIKE $%d OR ns.city ILIKE $%d OR ns.state ILIKE $%d OR ns.zip_code ILIKE $%d OR g.name ILIKE $%d)", argIdx, argIdx, argIdx, argIdx, argIdx)
		args = append(args, "%"+search+"%")
		argIdx++
	}
	if zip != "" {
		base += fmt.Sprintf(" AND ns.zip_code ILIKE $%d", argIdx)
		args = append(args, "%"+zip+"%")
		argIdx++
	}

	var total int
	if err := h.pool.QueryRow(ctx, "SELECT COUNT(*) "+base, args...).Scan(&total); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to count neighborhoods"})
		return
	}

	query := `
		SELECT
			ns.id,
			ns.name,
			ns.city,
			ns.state,
			COALESCE(ns.zip_code, ''),
			ns.country,
			ns.lat,
			ns.lng,
			ns.radius_meters,
			ns.created_at,
			ns.group_id,
			COALESCE(g.name, ''),
			COALESCE(g.member_count, 0),
			COALESCE((
				SELECT COUNT(*) FROM group_members gm
				WHERE gm.group_id = ns.group_id AND gm.role IN ('owner','admin')
			), 0) AS admin_count,
			COALESCE((
				SELECT COUNT(*) FROM board_entries be
				WHERE be.is_active = TRUE
				  AND ST_DWithin(be.location, ST_SetSRID(ST_MakePoint(ns.lng, ns.lat), 4326)::geography, ns.radius_meters)
			), 0) AS board_post_count,
			COALESCE((
				SELECT COUNT(*) FROM group_posts gp
				WHERE gp.group_id = ns.group_id AND gp.is_deleted = FALSE
			), 0) AS group_post_count
	` + base + fmt.Sprintf(" ORDER BY %s %s, ns.created_at DESC LIMIT $%d OFFSET $%d", sortColumn, order, argIdx, argIdx+1)

	args = append(args, limit, offset)
	rows, err := h.pool.Query(ctx, query, args...)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list neighborhoods"})
		return
	}
	defer rows.Close()

	items := make([]gin.H, 0)
	for rows.Next() {
		var id uuid.UUID
		var name, city, state, zipCode, country string
		var lat, lng float64
		var radiusMeters, memberCount, adminCount, boardCount, groupPostCount int
		var createdAt time.Time
		var groupID *uuid.UUID
		var groupName string

		if err := rows.Scan(
			&id, &name, &city, &state, &zipCode, &country,
			&lat, &lng, &radiusMeters, &createdAt,
			&groupID, &groupName, &memberCount, &adminCount, &boardCount, &groupPostCount,
		); err != nil {
			continue
		}

		items = append(items, gin.H{
			"id":               id,
			"name":             name,
			"city":             city,
			"state":            state,
			"zip_code":         zipCode,
			"country":          country,
			"lat":              lat,
			"lng":              lng,
			"radius_meters":    radiusMeters,
			"created_at":       createdAt,
			"group_id":         groupID,
			"group_name":       groupName,
			"member_count":     memberCount,
			"admin_count":      adminCount,
			"board_post_count": boardCount,
			"group_post_count": groupPostCount,
		})
	}

	c.JSON(http.StatusOK, gin.H{
		"neighborhoods": items,
		"total":         total,
		"limit":         limit,
		"offset":        offset,
	})
}

// SetNeighborhoodAdmin assigns or removes neighborhood admins by role on group_members.
func (h *AdminHandler) SetNeighborhoodAdmin(c *gin.Context) {
	ctx := c.Request.Context()
	seedID := c.Param("id")

	var req struct {
		UserID string `json:"user_id" binding:"required"`
		Action string `json:"action" binding:"required,oneof=assign remove"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	var groupID uuid.UUID
	if err := h.pool.QueryRow(ctx, `SELECT group_id FROM neighborhood_seeds WHERE id = $1::uuid`, seedID).Scan(&groupID); err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "neighborhood or group not found"})
		return
	}

	if req.Action == "assign" {
		_, err := h.pool.Exec(ctx, `
			INSERT INTO group_members (group_id, user_id, role)
			VALUES ($1, $2::uuid, 'admin')
			ON CONFLICT (group_id, user_id) DO UPDATE SET role = 'admin'
		`, groupID, req.UserID)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to assign admin"})
			return
		}
		c.JSON(http.StatusOK, gin.H{"message": "admin assigned"})
		return
	}

	_, err := h.pool.Exec(ctx, `
		UPDATE group_members
		SET role = 'member'
		WHERE group_id = $1 AND user_id = $2::uuid AND role = 'admin'
	`, groupID, req.UserID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to remove admin"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "admin removed"})
}

// ListNeighborhoodAdmins returns current owner/admin members for a neighborhood group.
func (h *AdminHandler) ListNeighborhoodAdmins(c *gin.Context) {
	ctx := c.Request.Context()
	seedID := c.Param("id")

	var groupID uuid.UUID
	if err := h.pool.QueryRow(ctx, `SELECT group_id FROM neighborhood_seeds WHERE id = $1::uuid`, seedID).Scan(&groupID); err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "neighborhood or group not found"})
		return
	}

	rows, err := h.pool.Query(ctx, `
		SELECT gm.user_id, gm.role, gm.joined_at,
		       COALESCE(p.handle, ''), COALESCE(p.display_name, ''), COALESCE(p.avatar_url, '')
		FROM group_members gm
		JOIN profiles p ON p.id = gm.user_id
		WHERE gm.group_id = $1 AND gm.role IN ('owner', 'admin')
		ORDER BY CASE gm.role WHEN 'owner' THEN 0 ELSE 1 END, gm.joined_at ASC
	`, groupID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list neighborhood admins"})
		return
	}
	defer rows.Close()

	admins := make([]gin.H, 0)
	for rows.Next() {
		var userID uuid.UUID
		var role, handle, displayName, avatarURL string
		var joinedAt time.Time
		if err := rows.Scan(&userID, &role, &joinedAt, &handle, &displayName, &avatarURL); err != nil {
			continue
		}
		admins = append(admins, gin.H{
			"user_id":      userID,
			"role":         role,
			"joined_at":    joinedAt,
			"handle":       handle,
			"display_name": displayName,
			"avatar_url":   avatarURL,
		})
	}

	c.JSON(http.StatusOK, gin.H{"admins": admins})
}

// ListNeighborhoodBoardEntries lists board content inside a neighborhood radius for moderation.
func (h *AdminHandler) ListNeighborhoodBoardEntries(c *gin.Context) {
	ctx := c.Request.Context()
	seedID := c.Param("id")
	limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
	offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
	search := strings.TrimSpace(c.Query("search"))
	active := strings.TrimSpace(c.Query("active"))

	if limit <= 0 {
		limit = 50
	}
	if limit > 200 {
		limit = 200
	}

	var lat, lng float64
	var radius int
	if err := h.pool.QueryRow(ctx, `SELECT lat, lng, radius_meters FROM neighborhood_seeds WHERE id = $1::uuid`, seedID).Scan(&lat, &lng, &radius); err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "neighborhood not found"})
		return
	}

	base := `
		FROM board_entries be
		JOIN profiles p ON p.id = be.author_id
		WHERE ST_DWithin(be.location, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography, $3)
	`
	args := []any{lng, lat, radius}
	argIdx := 4

	if search != "" {
		base += fmt.Sprintf(" AND be.body ILIKE $%d", argIdx)
		args = append(args, "%"+search+"%")
		argIdx++
	}
	if active == "true" || active == "false" {
		base += fmt.Sprintf(" AND be.is_active = $%d", argIdx)
		args = append(args, active == "true")
		argIdx++
	}

	var total int
	if err := h.pool.QueryRow(ctx, "SELECT COUNT(*) "+base, args...).Scan(&total); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to count board entries"})
		return
	}

	query := `
		SELECT be.id, be.body, be.topic, be.is_active, be.is_pinned, be.upvotes, be.reply_count, be.created_at,
		       p.id, COALESCE(p.handle, ''), COALESCE(p.display_name, '')
	` + base + fmt.Sprintf(" ORDER BY be.is_pinned DESC, be.created_at DESC LIMIT $%d OFFSET $%d", argIdx, argIdx+1)
	args = append(args, limit, offset)

	rows, err := h.pool.Query(ctx, query, args...)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list board entries"})
		return
	}
	defer rows.Close()

	entries := make([]gin.H, 0)
	for rows.Next() {
		var id, authorID uuid.UUID
		var body, topic, handle, displayName string
		var isActive, isPinned bool
		var upvotes, replyCount int
		var createdAt time.Time
		if err := rows.Scan(&id, &body, &topic, &isActive, &isPinned, &upvotes, &replyCount, &createdAt, &authorID, &handle, &displayName); err != nil {
			continue
		}
		entries = append(entries, gin.H{
			"id":          id,
			"body":        body,
			"topic":       topic,
			"is_active":   isActive,
			"is_pinned":   isPinned,
			"upvotes":     upvotes,
			"reply_count": replyCount,
			"created_at":  createdAt,
			"author": gin.H{
				"id":           authorID,
				"handle":       handle,
				"display_name": displayName,
			},
		})
	}

	c.JSON(http.StatusOK, gin.H{
		"entries": entries,
		"total":   total,
		"limit":   limit,
		"offset":  offset,
	})
}

func (h *AdminHandler) UpdateNeighborhoodBoardEntry(c *gin.Context) {
	ctx := c.Request.Context()
	entryID := c.Param("entryId")

	var req struct {
		IsActive *bool `json:"is_active"`
		IsPinned *bool `json:"is_pinned"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if req.IsActive == nil && req.IsPinned == nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "is_active or is_pinned is required"})
		return
	}

	sets := make([]string, 0, 2)
	args := make([]any, 0, 3)
	argIdx := 1
	if req.IsActive != nil {
		sets = append(sets, fmt.Sprintf("is_active = $%d", argIdx))
		args = append(args, *req.IsActive)
		argIdx++
	}
	if req.IsPinned != nil {
		sets = append(sets, fmt.Sprintf("is_pinned = $%d", argIdx))
		args = append(args, *req.IsPinned)
		argIdx++
	}
	sets = append(sets, "updated_at = NOW()")
	args = append(args, entryID)

	query := fmt.Sprintf("UPDATE board_entries SET %s WHERE id = $%d::uuid", strings.Join(sets, ", "), argIdx)
	_, err := h.pool.Exec(ctx, query, args...)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update board entry"})
		return
	}

	resp := gin.H{"message": "board entry updated"}
	if req.IsActive != nil {
		resp["is_active"] = *req.IsActive
	}
	if req.IsPinned != nil {
		resp["is_pinned"] = *req.IsPinned
	}

	c.JSON(http.StatusOK, resp)
}
