package repository

import (
	"backend/pkg/chat/domain/chat"
	"backend/pkg/chat/domain/entity"
	"backend/pkg/chat/domain/repository"
	"backend/pkg/library/cassandra"
	"context"
	"fmt"
	"time"

	"github.com/gocql/gocql"
)

type roomRepository struct {
	roomRepo     cassandra.Repository[entity.Room]
	memberRepo   cassandra.Repository[entity.RoomMember]
	userRoomRepo cassandra.Repository[entity.UserRoom]
	db           *cassandra.DB
	keyspace     string
}

// RoomRepositoryParam 創建 RoomRepository 所需的參數
type RoomRepositoryParam struct {
	DB      *cassandra.DB
	Keyspace string
}

// MustRoomRepository 創建 RoomRepository（如果失敗會 panic）
func MustRoomRepository(param RoomRepositoryParam) repository.RoomRepository {
	repo, err := NewRoomRepository(param.DB, param.Keyspace)
	if err != nil {
		panic(fmt.Sprintf("failed to create room repository: %v", err))
	}
	return repo
}

// NewRoomRepository 創建新的聊天室 Repository
func NewRoomRepository(db *cassandra.DB, keyspace string) (repository.RoomRepository, error) {
	roomRepo, err := cassandra.NewRepository[entity.Room](db, keyspace)
	if err != nil {
		return nil, err
	}

	memberRepo, err := cassandra.NewRepository[entity.RoomMember](db, keyspace)
	if err != nil {
		return nil, err
	}

	userRoomRepo, err := cassandra.NewRepository[entity.UserRoom](db, keyspace)
	if err != nil {
		return nil, err
	}

	return &roomRepository{
		roomRepo:     roomRepo,
		memberRepo:   memberRepo,
		userRoomRepo: userRoomRepo,
		db:           db,
		keyspace:     keyspace,
	}, nil
}

// ==================== Room Interface 實作 ====================

func (r *roomRepository) Create(ctx context.Context, room *entity.Room) error {
	now := time.Now().UTC().UnixNano()
	if room.CreatedAt == 0 {
		room.CreatedAt = now
	}
	if room.UpdatedAt == 0 {
		room.UpdatedAt = now
	}
	room.RoomID = gocql.TimeUUID()

	return r.roomRepo.Insert(ctx, *room)
}

func (r *roomRepository) RoomGet(ctx context.Context, roomID string) (*entity.Room, error) {
	uuid, err := gocql.ParseUUID(roomID)
	if err != nil {
		return nil, err
	}
	room, err := r.roomRepo.Get(ctx, entity.Room{
		RoomID: uuid,
	})
	if err != nil {
		if cassandra.IsNotFound(err) {
			return nil, cassandra.ErrNotFound
		}

		return nil, err
	}

	return &room, nil
}

func (r *roomRepository) RoomUpdate(ctx context.Context, room *entity.Room) error {
	update := entity.Room{}
	now := time.Now().UTC().UnixNano()

	get, err := r.RoomGet(ctx, room.RoomID.String())
	if err != nil {
		return err
	}

	update.CreatedAt = get.CreatedAt
	update.UpdatedAt = now
	update.Status = room.Status
	update.Name = room.Name
	update.RoomID = room.RoomID

	if err = r.roomRepo.Update(ctx, update); err != nil {
		return err
	}

	return nil
}

func (r *roomRepository) RoomDelete(ctx context.Context, roomID string) error {
	uuid, err := gocql.ParseUUID(roomID)
	if err != nil {
		return err
	}

	// 使用原生 CQL 語句來刪除，避免 cassandra library Delete 方法的 struct 綁定問題
	e := entity.Room{}
	stmt := fmt.Sprintf("DELETE FROM %s.%s WHERE room_id = ?", r.keyspace, e.TableName())
	return r.db.GetSession().Query(stmt, nil).
		Bind(uuid).
		WithContext(ctx).
		ExecRelease()
}

func (r *roomRepository) RoomList(ctx context.Context, param repository.ListRoomsReq) ([]entity.Room, error) {
	query := r.roomRepo.Query()

	if param.Status != "" {
		query = query.Where(cassandra.Eq("status", param.Status)).AllowFiltering()
	}

	if param.PageSize > 0 {
		query = query.Limit(param.PageSize)
	}

	if param.LastID != "" {
		lastUUID, err := gocql.ParseUUID(param.LastID)
		if err != nil {
			return nil, err
		}
		query = query.Where(cassandra.Lt("room_id", lastUUID))
	}

	var rooms []entity.Room
	if err := query.Scan(ctx, &rooms); err != nil {
		return nil, err
	}

	return rooms, nil
}

func (r *roomRepository) RoomCount(ctx context.Context, param repository.CountRoomsReq) (int64, error) {
	query := r.roomRepo.Query()
	if param.Status != "" {
		query = query.Where(cassandra.Eq("status", param.Status)).AllowFiltering()
	}
	return query.Count(ctx)
}

func (r *roomRepository) RoomExists(ctx context.Context, roomID string) (bool, error) {
	uuid, err := gocql.ParseUUID(roomID)
	if err != nil {
		return false, err
	}
	_, err = r.roomRepo.Get(ctx, entity.Room{
		RoomID: uuid,
	})

	if err != nil {
		if cassandra.IsNotFound(err) {
			return false, nil
		}

		return false, err
	}

	return true, nil
}

func (r *roomRepository) RoomGetByID(ctx context.Context, roomIDs []string) ([]entity.Room, error) {
	if len(roomIDs) == 0 {
		return []entity.Room{}, nil
	}

	// 將字串 ID 轉換為 UUID
	uuids := make([]any, 0, len(roomIDs))
	for _, id := range roomIDs {
		uuid, err := gocql.ParseUUID(id)
		if err != nil {
			return nil, err
		}
		uuids = append(uuids, uuid)
	}

	var rooms []entity.Room
	err := r.roomRepo.Query().
		Where(cassandra.In("room_id", uuids)).
		Scan(ctx, &rooms)
	if err != nil {
		return nil, err
	}

	return rooms, nil
}

// ==================== Member Interface 實作 ====================

func (r *roomRepository) Insert(ctx context.Context, member *entity.RoomMember) error {
	now := time.Now().UTC().UnixNano()
	if member.JoinedAt == 0 {
		member.JoinedAt = now
	}
	if member.Role == "" {
		member.Role = chat.RoomRoleMember.String()
	}

	// 同時插入到 user_room 表（反向查詢表）
	userRoom := entity.UserRoom{
		UID:      member.UID,
		RoomID:   member.RoomID,
		JoinedAt: member.JoinedAt,
	}

	if err := r.memberRepo.Insert(ctx, *member); err != nil {
		return err
	}

	if err := r.userRoomRepo.Insert(ctx, userRoom); err != nil {
		return err
	}

	return nil
}

func (r *roomRepository) Get(ctx context.Context, roomID, uid string) (*entity.RoomMember, error) {
	uuid, err := gocql.ParseUUID(roomID)
	if err != nil {
		return nil, err
	}

	member, err := r.memberRepo.Get(ctx, entity.RoomMember{
		RoomID: uuid,
		UID:    uid,
	})
	if err != nil {
		if cassandra.IsNotFound(err) {
			return nil, cassandra.ErrNotFound
		}
		return nil, err
	}
	return &member, nil
}

func (r *roomRepository) AllMembers(ctx context.Context, roomID string) ([]entity.RoomMember, error) {
	uuid, err := gocql.ParseUUID(roomID)
	if err != nil {
		return nil, err
	}

	var members []entity.RoomMember
	err = r.memberRepo.Query().
		Where(cassandra.Eq("room_id", uuid)).
		Scan(ctx, &members)
	if err != nil {
		return nil, err
	}
	return members, nil
}

func (r *roomRepository) UpdateRole(ctx context.Context, member *entity.RoomMember) error {
	get, err := r.Get(ctx, member.RoomID.String(), member.UID)
	if err != nil {
		return err
	}

	update := entity.RoomMember{
		RoomID:   member.RoomID,
		UID:      member.UID,
		Role:     member.Role,
		JoinedAt: get.JoinedAt,
	}

	return r.memberRepo.Update(ctx, update)
}

func (r *roomRepository) DeleteMember(ctx context.Context, roomID, uid string) error {
	uuid, err := gocql.ParseUUID(roomID)
	if err != nil {
		return err
	}

	// 同時從兩個表中刪除
	if err := r.memberRepo.Delete(ctx, entity.RoomMember{
		RoomID: uuid,
		UID:    uid,
	}); err != nil {
		return err
	}

	if err := r.userRoomRepo.Delete(ctx, entity.UserRoom{
		UID:    uid,
		RoomID: uuid,
	}); err != nil {
		return err
	}

	return nil
}

func (r *roomRepository) DeleteRoom(ctx context.Context, roomID string) error {
	uuid, err := gocql.ParseUUID(roomID)
	if err != nil {
		return err
	}

	// 先查詢所有成員（必須在刪除之前查詢）
	members, err := r.AllMembers(ctx, roomID)
	if err != nil {
		return err
	}

	// 刪除 user_room 表中的所有關聯
	for _, member := range members {
		if err := r.userRoomRepo.Delete(ctx, entity.UserRoom{
			UID:    member.UID,
			RoomID: uuid,
		}); err != nil {
			return err
		}
	}

	// 最後刪除 room_member 表中的所有成員
	e := entity.RoomMember{}
	stmt := fmt.Sprintf("DELETE FROM %s.%s WHERE room_id = ?", r.keyspace, e.TableName())
	if err := r.db.GetSession().Query(stmt, nil).
		Bind(uuid).
		WithContext(ctx).
		WithTimestamp(time.Now().UnixNano() / 1e3).
		ExecRelease(); err != nil {
		return err
	}

	return nil
}

func (r *roomRepository) Count(ctx context.Context, roomID string) (int64, error) {
	uuid, err := gocql.ParseUUID(roomID)
	if err != nil {
		return 0, err
	}

	return r.memberRepo.Query().
		Where(cassandra.Eq("room_id", uuid)).
		Count(ctx)
}

// ==================== User Interface 實作 ====================

func (r *roomRepository) GetUserRooms(ctx context.Context, uid string) ([]entity.UserRoom, error) {
	var userRooms []entity.UserRoom
	err := r.userRoomRepo.Query().
		Where(cassandra.Eq("uid", uid)).
		Scan(ctx, &userRooms)
	if err != nil {
		return nil, err
	}

	return userRooms, nil
}

func (r *roomRepository) CountUserRooms(ctx context.Context, uid string) (int64, error) {
	return r.userRoomRepo.Query().
		Where(cassandra.Eq("uid", uid)).
		Count(ctx)
}

func (r *roomRepository) IsUserInRoom(ctx context.Context, uid, roomID string) (bool, error) {
	uuid, err := gocql.ParseUUID(roomID)
	if err != nil {
		return false, err
	}

	_, err = r.userRoomRepo.Get(ctx, entity.UserRoom{
		UID:    uid,
		RoomID: uuid,
	})
	if err != nil {
		if cassandra.IsNotFound(err) {
			return false, nil
		}
		return false, err
	}
	return true, nil
}
