package services

import (
	"context"
	"fmt"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5/pgxpool"

	"gitlab.com/patrickbritton3/sojorn/go-backend/internal/models"
)

type GroupService struct {
	pool *pgxpool.Pool
}

func NewGroupService(pool *pgxpool.Pool) *GroupService {
	return &GroupService{pool: pool}
}

// CreateGeoGroup creates a new geo-cluster (neighborhood) group at the given coordinates.
func (s *GroupService) CreateGeoGroup(ctx context.Context, name string, lat, long float64, radiusMeters int, createdBy uuid.UUID) (*models.Group, error) {
	var group models.Group
	err := s.pool.QueryRow(ctx, `
		INSERT INTO groups (name, type, privacy, location_center, radius_meters, created_by, member_count)
		VALUES ($1, 'geo', 'public', ST_SetSRID(ST_MakePoint($2, $3), 4326)::geography, $4, $5, 0)
		RETURNING id, name, description, type, privacy, radius_meters, member_count, is_active, category, created_at, updated_at
	`, name, long, lat, radiusMeters, createdBy).Scan(
		&group.ID, &group.Name, &group.Description, &group.Type, &group.Privacy,
		&group.RadiusMeters, &group.MemberCount, &group.IsActive, &group.Category, &group.CreatedAt, &group.UpdatedAt,
	)
	if err != nil {
		return nil, fmt.Errorf("create geo group: %w", err)
	}
	group.Lat = &lat
	group.Long = &long
	group.CreatedBy = &createdBy
	return &group, nil
}

// FindNearestGeoGroup finds the closest geo group to the given coordinates within maxDistance meters.
func (s *GroupService) FindNearestGeoGroup(ctx context.Context, lat, long float64, maxDistanceMeters int) (*models.GroupWithDistance, error) {
	var gwd models.GroupWithDistance
	err := s.pool.QueryRow(ctx, `
		SELECT g.id, g.name, g.description, g.type, g.privacy, g.radius_meters,
		       g.member_count, g.is_active, g.category, g.created_at, g.updated_at,
		       ST_Distance(g.location_center, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography) AS distance_meters
		FROM groups g
		WHERE g.type = 'geo' AND g.is_active = TRUE
		  AND ST_DWithin(g.location_center, ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography, $3)
		ORDER BY distance_meters ASC
		LIMIT 1
	`, long, lat, maxDistanceMeters).Scan(
		&gwd.ID, &gwd.Name, &gwd.Description, &gwd.Type, &gwd.Privacy,
		&gwd.RadiusMeters, &gwd.MemberCount, &gwd.IsActive, &gwd.Category, &gwd.CreatedAt, &gwd.UpdatedAt,
		&gwd.DistanceMeters,
	)
	if err != nil {
		return nil, fmt.Errorf("find nearest geo group: %w", err)
	}
	return &gwd, nil
}

// AutoJoinNearestGeoGroup finds the nearest geo group and adds the user as a member.
// Returns the group (or nil if none found). Idempotent — won't duplicate memberships.
func (s *GroupService) AutoJoinNearestGeoGroup(ctx context.Context, userID uuid.UUID, lat, long float64) (*models.Group, error) {
	gwd, err := s.FindNearestGeoGroup(ctx, lat, long, 50000) // 50km max
	if err != nil {
		return nil, nil // no group found — that's OK
	}

	_, err = s.pool.Exec(ctx, `
		INSERT INTO group_members (group_id, user_id, role)
		VALUES ($1, $2, 'member')
		ON CONFLICT (group_id, user_id) DO NOTHING
	`, gwd.ID, userID)
	if err != nil {
		return nil, fmt.Errorf("auto-join geo group: %w", err)
	}

	// Bump member count (best-effort)
	s.pool.Exec(ctx, `
		UPDATE groups SET member_count = (
			SELECT COUNT(*) FROM group_members WHERE group_id = $1
		) WHERE id = $1
	`, gwd.ID)

	return &gwd.Group, nil
}

// GetUserGeoGroup returns the user's current geo group (if any).
func (s *GroupService) GetUserGeoGroup(ctx context.Context, userID uuid.UUID) (*models.Group, error) {
	var group models.Group
	var createdAt, updatedAt time.Time
	err := s.pool.QueryRow(ctx, `
		SELECT g.id, g.name, g.description, g.type, g.privacy, g.radius_meters,
		       g.member_count, g.is_active, g.category, g.created_at, g.updated_at
		FROM groups g
		JOIN group_members gm ON gm.group_id = g.id
		WHERE gm.user_id = $1 AND g.type = 'geo' AND g.is_active = TRUE
		ORDER BY gm.joined_at DESC
		LIMIT 1
	`, userID).Scan(
		&group.ID, &group.Name, &group.Description, &group.Type, &group.Privacy,
		&group.RadiusMeters, &group.MemberCount, &group.IsActive, &group.Category, &createdAt, &updatedAt,
	)
	if err != nil {
		return nil, fmt.Errorf("get user geo group: %w", err)
	}
	group.CreatedAt = createdAt
	group.UpdatedAt = updatedAt
	return &group, nil
}

// GetGroupPosts returns posts belonging to a group, optionally filtered by beacon-only.
func (s *GroupService) GetGroupPosts(ctx context.Context, groupID uuid.UUID, beaconsOnly bool, limit, offset int) ([]models.Post, error) {
	var filter string
	if beaconsOnly {
		filter = " AND p.is_beacon = TRUE"
	}

	rows, err := s.pool.Query(ctx, fmt.Sprintf(`
		SELECT p.id, p.author_id, p.body, p.status, p.is_beacon, p.beacon_type,
		       p.severity, p.incident_status, p.radius, p.created_at, p.group_id
		FROM posts p
		WHERE p.group_id = $1 AND p.status = 'active' AND p.deleted_at IS NULL%s
		ORDER BY p.created_at DESC
		LIMIT $2 OFFSET $3
	`, filter), groupID, limit, offset)
	if err != nil {
		return nil, fmt.Errorf("get group posts: %w", err)
	}
	defer rows.Close()

	var posts []models.Post
	for rows.Next() {
		var p models.Post
		if err := rows.Scan(
			&p.ID, &p.AuthorID, &p.Body, &p.Status, &p.IsBeacon, &p.BeaconType,
			&p.Severity, &p.IncidentStatus, &p.Radius, &p.CreatedAt, &p.GroupID,
		); err != nil {
			continue
		}
		posts = append(posts, p)
	}
	return posts, nil
}

// GetGroupByID returns a single group by its ID.
func (s *GroupService) GetGroupByID(ctx context.Context, groupID uuid.UUID) (*models.Group, error) {
	var group models.Group
	err := s.pool.QueryRow(ctx, `
		SELECT id, name, description, type, privacy, radius_meters,
		       member_count, is_active, category, created_at, updated_at
		FROM groups WHERE id = $1
	`, groupID).Scan(
		&group.ID, &group.Name, &group.Description, &group.Type, &group.Privacy,
		&group.RadiusMeters, &group.MemberCount, &group.IsActive, &group.Category, &group.CreatedAt, &group.UpdatedAt,
	)
	if err != nil {
		return nil, fmt.Errorf("get group: %w", err)
	}
	return &group, nil
}
