package social

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"time"

	"github.com/google/uuid"

	"scrabble/backend/internal/account"
)

// AdminMessage is one chat message in the admin moderation list: the message
// plus its sender's resolved display name and source, for the operator console.
type AdminMessage struct {
	ID         uuid.UUID
	GameID     uuid.UUID
	SenderID   uuid.UUID
	SenderName string
	// Source is the sender's account kind: "guest", "robot", or its oldest identity kind
	// (e.g. "email", "telegram"); "—" when it has none.
	Source    string
	Body      string
	SenderIP  string
	CreatedAt time.Time
	// UnreadSeats is the message's unread bitmask; non-zero means at least one recipient
	// has not read it. The console shows a plain read/unread flag derived from it.
	UnreadSeats int16
}

// AdminMessageFilter narrows the admin message list. A nil GameID/SenderID leaves that
// field unfiltered; NameMask/ExtMask are glob masks (account.LikePattern) matched
// case-insensitively against the sender's display name / any identity's external id;
// UnreadOnly keeps only messages still unread by at least one recipient.
type AdminMessageFilter struct {
	GameID     uuid.UUID
	SenderID   uuid.UUID
	NameMask   string
	ExtMask    string
	UnreadOnly bool
}

// AdminListMessages returns the filtered chat messages — real messages only, nudges
// excluded — newest first, paginated, for the admin moderation console.
func (svc *Service) AdminListMessages(ctx context.Context, f AdminMessageFilter, limit, offset int) ([]AdminMessage, error) {
	return svc.store.adminListMessages(ctx, f, limit, offset)
}

// AdminCountMessages counts the filtered chat messages, for the admin list pager.
func (svc *Service) AdminCountMessages(ctx context.Context, f AdminMessageFilter) (int, error) {
	return svc.store.adminCountMessages(ctx, f)
}

// adminMessageSource is the SQL CASE projecting a sender's source: guest, robot, or its
// oldest identity kind ("—" when it has none).
const adminMessageSource = `CASE
	WHEN a.is_guest THEN 'guest'
	WHEN EXISTS (SELECT 1 FROM backend.identities i WHERE i.account_id = a.account_id AND i.kind = 'robot') THEN 'robot'
	ELSE COALESCE((SELECT i2.kind FROM backend.identities i2 WHERE i2.account_id = a.account_id ORDER BY i2.created_at ASC LIMIT 1), '—')
END`

// adminMessageWhere builds the shared WHERE clause and its positional args (from $1).
// Only real messages are listed; nudges are excluded.
func adminMessageWhere(f AdminMessageFilter) (string, []any) {
	where := `m.kind = 'message'`
	var args []any
	if f.GameID != uuid.Nil {
		args = append(args, f.GameID)
		where += fmt.Sprintf(` AND m.game_id = $%d`, len(args))
	}
	if f.SenderID != uuid.Nil {
		args = append(args, f.SenderID)
		where += fmt.Sprintf(` AND m.sender_id = $%d`, len(args))
	}
	if name := account.LikePattern(f.NameMask); name != "" {
		args = append(args, name)
		where += fmt.Sprintf(` AND a.display_name ILIKE $%d ESCAPE '\'`, len(args))
	}
	if ext := account.LikePattern(f.ExtMask); ext != "" {
		args = append(args, ext)
		where += fmt.Sprintf(` AND EXISTS (SELECT 1 FROM backend.identities ie WHERE ie.account_id = a.account_id AND ie.external_id ILIKE $%d ESCAPE '\')`, len(args))
	}
	if f.UnreadOnly {
		where += ` AND m.unread_seats <> 0`
	}
	return where, args
}

func (s *Store) adminListMessages(ctx context.Context, f AdminMessageFilter, limit, offset int) ([]AdminMessage, error) {
	where, args := adminMessageWhere(f)
	q := `SELECT m.message_id, m.game_id, m.sender_id, a.display_name, ` + adminMessageSource + ` AS source, m.body, COALESCE(m.sender_ip, ''), m.created_at, m.unread_seats
FROM backend.chat_messages m
JOIN backend.accounts a ON a.account_id = m.sender_id
WHERE ` + where +
		fmt.Sprintf(` ORDER BY m.created_at DESC LIMIT $%d OFFSET $%d`, len(args)+1, len(args)+2)
	args = append(args, limit, offset)
	rows, err := s.db.QueryContext(ctx, q, args...)
	if err != nil {
		return nil, fmt.Errorf("social: admin list messages: %w", err)
	}
	defer rows.Close()
	var out []AdminMessage
	for rows.Next() {
		var m AdminMessage
		if err := rows.Scan(&m.ID, &m.GameID, &m.SenderID, &m.SenderName, &m.Source, &m.Body, &m.SenderIP, &m.CreatedAt, &m.UnreadSeats); err != nil {
			return nil, fmt.Errorf("social: scan admin message: %w", err)
		}
		out = append(out, m)
	}
	return out, rows.Err()
}

func (s *Store) adminCountMessages(ctx context.Context, f AdminMessageFilter) (int, error) {
	where, args := adminMessageWhere(f)
	var n int
	q := `SELECT COUNT(*) FROM backend.chat_messages m JOIN backend.accounts a ON a.account_id = m.sender_id WHERE ` + where
	if err := s.db.QueryRowContext(ctx, q, args...).Scan(&n); err != nil {
		return 0, fmt.Errorf("social: admin count messages: %w", err)
	}
	return n, nil
}

// AdminSeatRead is one game seat's status for a message's per-seat read breakdown: the
// seat index, its occupant's display name, and its role for the message — "sender",
// "read" or "unread". Still-empty (open) seats are omitted.
type AdminSeatRead struct {
	Seat        int
	AccountID   uuid.UUID
	DisplayName string
	Role        string
}

// AdminMessageCard is one chat message with the per-seat read breakdown, for the
// console's message detail page.
type AdminMessageCard struct {
	AdminMessage
	Kind  string
	Seats []AdminSeatRead
}

// AdminMessageDetail loads one chat message and the read status of every seated player
// in its game, for the moderation message card. The per-seat status is derived from the
// message's unread bitmask (a set bit is an unread recipient seat); the sender's own
// seat is marked "sender".
func (svc *Service) AdminMessageDetail(ctx context.Context, messageID uuid.UUID) (AdminMessageCard, error) {
	return svc.store.adminMessageCard(ctx, messageID)
}

func (s *Store) adminMessageCard(ctx context.Context, messageID uuid.UUID) (AdminMessageCard, error) {
	var d AdminMessageCard
	q := `SELECT m.game_id, m.sender_id, a.display_name, ` + adminMessageSource + ` AS source, m.kind, m.body, COALESCE(m.sender_ip, ''), m.created_at, m.unread_seats
FROM backend.chat_messages m
JOIN backend.accounts a ON a.account_id = m.sender_id
WHERE m.message_id = $1`
	if err := s.db.QueryRowContext(ctx, q, messageID).Scan(
		&d.GameID, &d.SenderID, &d.SenderName, &d.Source, &d.Kind, &d.Body, &d.SenderIP, &d.CreatedAt, &d.UnreadSeats,
	); err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return AdminMessageCard{}, ErrMessageNotFound
		}
		return AdminMessageCard{}, fmt.Errorf("social: admin message card: %w", err)
	}
	d.ID = messageID
	rows, err := s.db.QueryContext(ctx,
		`SELECT p.seat, COALESCE(p.account_id::text, ''), p.display_name
FROM backend.game_players p WHERE p.game_id = $1 ORDER BY p.seat`, d.GameID)
	if err != nil {
		return AdminMessageCard{}, fmt.Errorf("social: admin message seats: %w", err)
	}
	defer rows.Close()
	for rows.Next() {
		var (
			seat    int
			accStr  string
			seatNam string
		)
		if err := rows.Scan(&seat, &accStr, &seatNam); err != nil {
			return AdminMessageCard{}, fmt.Errorf("social: scan message seat: %w", err)
		}
		if accStr == "" {
			continue // a still-empty (open) seat has no reader
		}
		accID, _ := uuid.Parse(accStr)
		sr := AdminSeatRead{Seat: seat, AccountID: accID, DisplayName: seatNam}
		switch {
		case accID == d.SenderID:
			sr.Role = "sender"
		case d.UnreadSeats&(int16(1)<<uint(seat)) != 0:
			sr.Role = "unread"
		default:
			sr.Role = "read"
		}
		d.Seats = append(d.Seats, sr)
	}
	return d, rows.Err()
}
