package database

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

	"github.com/google/uuid"
)

// ChatRepository provides access to chat data
type ChatRepository struct {
	db *DB
}

// NewChatRepository creates a new chat repository
func NewChatRepository(db *DB) *ChatRepository {
	return &ChatRepository{db: db}
}

// CreateMessage creates a new message
func (r *ChatRepository) CreateMessage(ctx context.Context, message *Message) error {
	query := `
		INSERT INTO messages (room_id, user_id, content, type, parent_id, is_edited, is_deleted, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
		RETURNING id
	`

	err := r.db.QueryRowContext(ctx, query,
		message.RoomID,
		message.UserID,
		message.Content,
		message.Type,
		message.ParentID,
		message.IsEdited,
		message.IsDeleted,
		message.CreatedAt,
		message.UpdatedAt,
	).Scan(&message.ID)

	return err
}

// GetMessages retrieves messages for a room with pagination
func (r *ChatRepository) GetMessages(ctx context.Context, roomID uuid.UUID, page, limit int, beforeID *uuid.UUID) ([]*Message, error) {
	var query string
	var args []interface{}

	if beforeID != nil {
		query = `
			SELECT id, room_id, user_id, content, type, parent_id, is_edited, is_deleted, created_at, updated_at
			FROM messages 
			WHERE room_id = $1 AND id < $2 AND is_deleted = false
			ORDER BY created_at DESC
			LIMIT $3 OFFSET $4
		`
		args = []interface{}{roomID, *beforeID, limit, (page - 1) * limit}
	} else {
		query = `
			SELECT id, room_id, user_id, content, type, parent_id, is_edited, is_deleted, created_at, updated_at
			FROM messages 
			WHERE room_id = $1 AND is_deleted = false
			ORDER BY created_at DESC
			LIMIT $2 OFFSET $3
		`
		args = []interface{}{roomID, limit, (page - 1) * limit}
	}

	rows, err := r.db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var messages []*Message
	for rows.Next() {
		msg := &Message{}
		err := rows.Scan(
			&msg.ID,
			&msg.RoomID,
			&msg.UserID,
			&msg.Content,
			&msg.Type,
			&msg.ParentID,
			&msg.IsEdited,
			&msg.IsDeleted,
			&msg.CreatedAt,
			&msg.UpdatedAt,
		)
		if err != nil {
			return nil, err
		}
		messages = append(messages, msg)
	}

	return messages, nil
}

// GetMessageByID retrieves a message by ID
func (r *ChatRepository) GetMessageByID(ctx context.Context, messageID uuid.UUID) (*Message, error) {
	query := `
		SELECT id, room_id, user_id, content, type, parent_id, is_edited, is_deleted, created_at, updated_at
		FROM messages 
		WHERE id = $1
	`

	msg := &Message{}
	err := r.db.QueryRowContext(ctx, query, messageID).Scan(
		&msg.ID,
		&msg.RoomID,
		&msg.UserID,
		&msg.Content,
		&msg.Type,
		&msg.ParentID,
		&msg.IsEdited,
		&msg.IsDeleted,
		&msg.CreatedAt,
		&msg.UpdatedAt,
	)
	if err != nil {
		return nil, err
	}

	return msg, nil
}

// UpdateMessage updates a message
func (r *ChatRepository) UpdateMessage(ctx context.Context, message *Message) error {
	query := `
		UPDATE messages 
		SET content = $2, is_edited = $3, is_deleted = $4, updated_at = $5
		WHERE id = $1
	`

	_, err := r.db.ExecContext(ctx, query,
		message.ID,
		message.Content,
		message.IsEdited,
		message.IsDeleted,
		message.UpdatedAt,
	)

	return err
}

// CreateReaction creates a new reaction
func (r *ChatRepository) CreateReaction(ctx context.Context, reaction *Reaction) error {
	query := `
		INSERT INTO reactions (message_id, user_id, emoji, created_at)
		VALUES ($1, $2, $3, $4)
		RETURNING id
	`

	err := r.db.QueryRowContext(ctx, query,
		reaction.MessageID,
		reaction.UserID,
		reaction.Emoji,
		reaction.CreatedAt,
	).Scan(&reaction.ID)

	return err
}

// DeleteReaction removes a reaction
func (r *ChatRepository) DeleteReaction(ctx context.Context, messageID, userID uuid.UUID, emoji string) error {
	query := `DELETE FROM reactions WHERE message_id = $1 AND user_id = $2 AND emoji = $3`
	_, err := r.db.ExecContext(ctx, query, messageID, userID, emoji)
	return err
}

// CreateRoom creates a new room
func (r *ChatRepository) CreateRoom(ctx context.Context, room *Room) error {
	query := `
		INSERT INTO rooms (name, description, type, is_private, created_by, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7)
		RETURNING id
	`

	err := r.db.QueryRowContext(ctx, query,
		room.Name,
		room.Description,
		room.Type,
		room.IsPrivate,
		room.CreatedBy,
		room.CreatedAt,
		room.UpdatedAt,
	).Scan(&room.ID)

	return err
}

// GetRooms retrieves available rooms for a user
func (r *ChatRepository) GetRooms(ctx context.Context, userID uuid.UUID, includePrivate bool) ([]*Room, error) {
	var query string
	if includePrivate {
		query = `
			SELECT DISTINCT r.id, r.name, r.description, r.type, r.is_private, r.created_by, r.created_at, r.updated_at
			FROM rooms r
			LEFT JOIN room_members rm ON r.id = rm.room_id
			WHERE r.is_private = false OR rm.user_id = $1
			ORDER BY r.created_at DESC
		`
	} else {
		query = `
			SELECT id, name, description, type, is_private, created_by, created_at, updated_at
			FROM rooms 
			WHERE is_private = false
			ORDER BY created_at DESC
		`
	}

	var rows *sql.Rows
	var err error
	if includePrivate {
		rows, err = r.db.QueryContext(ctx, query, userID)
	} else {
		rows, err = r.db.QueryContext(ctx, query)
	}
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var rooms []*Room
	for rows.Next() {
		room := &Room{}
		err := rows.Scan(
			&room.ID,
			&room.Name,
			&room.Description,
			&room.Type,
			&room.IsPrivate,
			&room.CreatedBy,
			&room.CreatedAt,
			&room.UpdatedAt,
		)
		if err != nil {
			return nil, err
		}
		rooms = append(rooms, room)
	}

	return rooms, nil
}

// GetDirectMessageRoom retrieves or creates a DM room between two users
func (r *ChatRepository) GetDirectMessageRoom(ctx context.Context, userID1, userID2 uuid.UUID) (*Room, error) {
	query := `
		SELECT r.id, r.name, r.description, r.type, r.is_private, r.created_by, r.created_at, r.updated_at
		FROM rooms r
		JOIN room_members rm1 ON r.id = rm1.room_id
		JOIN room_members rm2 ON r.id = rm2.room_id
		WHERE r.type = 'dm' 
		AND rm1.user_id = $1 AND rm2.user_id = $2
		LIMIT 1
	`

	room := &Room{}
	err := r.db.QueryRowContext(ctx, query, userID1, userID2).Scan(
		&room.ID,
		&room.Name,
		&room.Description,
		&room.Type,
		&room.IsPrivate,
		&room.CreatedBy,
		&room.CreatedAt,
		&room.UpdatedAt,
	)
	if err != nil {
		return nil, err
	}

	return room, nil
}

// AddUserToRoom adds a user to a room
func (r *ChatRepository) AddUserToRoom(ctx context.Context, roomID, userID uuid.UUID) error {
	query := `
		INSERT INTO room_members (room_id, user_id, joined_at)
		VALUES ($1, $2, $3)
		ON CONFLICT (room_id, user_id) DO NOTHING
	`

	_, err := r.db.ExecContext(ctx, query, roomID, userID, time.Now())
	return err
}

// RemoveUserFromRoom removes a user from a room
func (r *ChatRepository) RemoveUserFromRoom(ctx context.Context, roomID, userID uuid.UUID) error {
	query := `DELETE FROM room_members WHERE room_id = $1 AND user_id = $2`
	_, err := r.db.ExecContext(ctx, query, roomID, userID)
	return err
}

// GetRoomUserCount gets the number of users in a room
func (r *ChatRepository) GetRoomUserCount(ctx context.Context, roomID uuid.UUID) (int, error) {
	query := `SELECT COUNT(*) FROM room_members WHERE room_id = $1`
	var count int
	err := r.db.QueryRowContext(ctx, query, roomID).Scan(&count)
	return count, err
}

// SearchMessages searches for messages in a room
func (r *ChatRepository) SearchMessages(ctx context.Context, roomID uuid.UUID, query string, limit int) ([]*Message, error) {
	sqlQuery := `
		SELECT id, room_id, user_id, content, type, parent_id, is_edited, is_deleted, created_at, updated_at
		FROM messages 
		WHERE room_id = $1 AND is_deleted = false AND content ILIKE $2
		ORDER BY created_at DESC
		LIMIT $3
	`

	searchPattern := "%" + query + "%"
	rows, err := r.db.QueryContext(ctx, sqlQuery, roomID, searchPattern, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var messages []*Message
	for rows.Next() {
		msg := &Message{}
		err := rows.Scan(
			&msg.ID,
			&msg.RoomID,
			&msg.UserID,
			&msg.Content,
			&msg.Type,
			&msg.ParentID,
			&msg.IsEdited,
			&msg.IsDeleted,
			&msg.CreatedAt,
			&msg.UpdatedAt,
		)
		if err != nil {
			return nil, err
		}
		messages = append(messages, msg)
	}

	return messages, nil
}
