import type { ChatConversation, ChatMessage, ChatThread, UserRole } from "@rental/shared";

import { query } from "../../db/pool.js";
import { ConflictError, NotFoundError } from "../../utils/app-error.js";

type UserRow = {
  id: string;
  name: string;
  role: UserRole;
  is_active: boolean;
};

type ChatMessageRow = {
  id: string;
  sender_id: string;
  sender_name: string;
  recipient_id: string;
  recipient_name: string;
  body: string;
  created_at: Date;
  read_at: Date | null;
};

type ChatThreadRow = {
  user_id: string;
  user_name: string;
  user_role: UserRole;
  unread_count: number;
  message_id: string | null;
  message_sender_id: string | null;
  message_sender_name: string | null;
  message_recipient_id: string | null;
  message_recipient_name: string | null;
  message_body: string | null;
  message_created_at: Date | null;
  message_read_at: Date | null;
};

const mapMessage = (row: ChatMessageRow): ChatMessage => ({
  id: row.id,
  senderId: row.sender_id,
  senderName: row.sender_name,
  recipientId: row.recipient_id,
  recipientName: row.recipient_name,
  body: row.body,
  createdAt: row.created_at.toISOString(),
  readAt: row.read_at ? row.read_at.toISOString() : null
});

const getActiveUserSummary = async (id: string) => {
  const result = await query<UserRow>(
    `
      select id, name, role, is_active
      from users
      where id = $1
    `,
    [id]
  );

  const user = result.rows[0];

  if (!user) {
    throw new NotFoundError("User not found");
  }

  if (!user.is_active) {
    throw new ConflictError("Selected user account is inactive");
  }

  return {
    id: user.id,
    name: user.name,
    role: user.role
  };
};

export const listChatThreads = async (currentUserId: string): Promise<ChatThread[]> => {
  const result = await query<ChatThreadRow>(
    `
      select
        u.id as user_id,
        u.name as user_name,
        u.role as user_role,
        coalesce(unread.unread_count, 0)::int as unread_count,
        last_message.id as message_id,
        last_message.sender_id as message_sender_id,
        sender.name as message_sender_name,
        last_message.recipient_id as message_recipient_id,
        recipient.name as message_recipient_name,
        last_message.body as message_body,
        last_message.created_at as message_created_at,
        last_message.read_at as message_read_at
      from users u
      left join lateral (
        select count(*)::int as unread_count
        from chat_messages m
        where m.sender_id = u.id
          and m.recipient_id = $1
          and m.read_at is null
      ) unread on true
      left join lateral (
        select m.*
        from chat_messages m
        where (m.sender_id = $1 and m.recipient_id = u.id)
           or (m.sender_id = u.id and m.recipient_id = $1)
        order by m.created_at desc
        limit 1
      ) last_message on true
      left join users sender on sender.id = last_message.sender_id
      left join users recipient on recipient.id = last_message.recipient_id
      where u.id <> $1
        and u.is_active = true
      order by
        coalesce(unread.unread_count, 0) desc,
        last_message.created_at desc nulls last,
        u.name asc
    `,
    [currentUserId]
  );

  return result.rows.map((row) => ({
    user: {
      id: row.user_id,
      name: row.user_name,
      role: row.user_role
    },
    unreadCount: row.unread_count,
    lastMessage:
      row.message_id && row.message_sender_id && row.message_recipient_id && row.message_created_at
        ? {
            id: row.message_id,
            senderId: row.message_sender_id,
            senderName: row.message_sender_name ?? "",
            recipientId: row.message_recipient_id,
            recipientName: row.message_recipient_name ?? "",
            body: row.message_body ?? "",
            createdAt: row.message_created_at.toISOString(),
            readAt: row.message_read_at ? row.message_read_at.toISOString() : null
          }
        : null
  }));
};

export const getConversation = async (currentUserId: string, withUserId: string): Promise<ChatConversation> => {
  if (currentUserId === withUserId) {
    throw new ConflictError("Cannot open a chat with the same user");
  }

  const user = await getActiveUserSummary(withUserId);

  await query(
    `
      update chat_messages
      set read_at = now()
      where sender_id = $1
        and recipient_id = $2
        and read_at is null
    `,
    [withUserId, currentUserId]
  );

  const result = await query<ChatMessageRow>(
    `
      select
        m.id,
        m.sender_id,
        sender.name as sender_name,
        m.recipient_id,
        recipient.name as recipient_name,
        m.body,
        m.created_at,
        m.read_at
      from chat_messages m
      join users sender on sender.id = m.sender_id
      join users recipient on recipient.id = m.recipient_id
      where (m.sender_id = $1 and m.recipient_id = $2)
         or (m.sender_id = $2 and m.recipient_id = $1)
      order by m.created_at asc
      limit 250
    `,
    [currentUserId, withUserId]
  );

  return {
    user,
    messages: result.rows.map(mapMessage)
  };
};

export const sendChatMessage = async (
  currentUserId: string,
  payload: {
    recipientId: string;
    body: string;
  }
): Promise<ChatMessage> => {
  if (currentUserId === payload.recipientId) {
    throw new ConflictError("Cannot send a message to the same user");
  }

  await getActiveUserSummary(payload.recipientId);

  const result = await query<ChatMessageRow>(
    `
      insert into chat_messages (sender_id, recipient_id, body)
      values ($1, $2, $3)
      returning
        id,
        sender_id,
        (select name from users where id = sender_id) as sender_name,
        recipient_id,
        (select name from users where id = recipient_id) as recipient_name,
        body,
        created_at,
        read_at
    `,
    [currentUserId, payload.recipientId, payload.body.trim()]
  );

  const message = result.rows[0];

  if (!message) {
    throw new Error("Chat message insert did not return a row");
  }

  return mapMessage(message);
};
