-- Chat schema for 1:1 and group conversations (Supabase-friendly)
-- Tables: chat_conversations, chat_participants, chat_messages, chat_notifications

create table if not exists chat_conversations (
  id uuid primary key default gen_random_uuid(),
  title text,
  is_group boolean not null default false,
  owner_id uuid references public.ak_users(id) on delete set null,
  last_message_at timestamptz,
  metadata jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);

create table if not exists chat_participants (
  id uuid primary key default gen_random_uuid(),
  conversation_id uuid not null references chat_conversations(id) on delete cascade,
  user_id uuid not null references public.ak_users(id) on delete cascade,
  role text not null default 'member' check (role in ('owner','admin','member')),
  joined_at timestamptz not null default now(),
  last_read_at timestamptz,
  is_muted boolean not null default false,
  settings jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now(),
  unique(conversation_id, user_id)
);

create table if not exists chat_messages (
  id uuid primary key default gen_random_uuid(),
  conversation_id uuid not null references chat_conversations(id) on delete cascade,
  sender_id uuid not null references public.ak_users(id) on delete cascade,
  content text not null,
  content_type text not null default 'text' check (content_type in ('text','markdown','json','image','file','audio')),
  reply_to uuid references chat_messages(id) on delete set null,
  metadata jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);

create index if not exists idx_chat_messages_conv_time on chat_messages(conversation_id, created_at desc);
create index if not exists idx_chat_participants_conv on chat_participants(conversation_id);
create index if not exists idx_chat_participants_user on chat_participants(user_id);
create index if not exists idx_chat_notifications_user_time on chat_notifications(user_id, created_at desc);

create table if not exists chat_notifications (
  id uuid primary key default gen_random_uuid(),
  user_id uuid not null references public.ak_users(id) on delete cascade,
  conversation_id uuid references chat_conversations(id) on delete cascade,
  message_id uuid references chat_messages(id) on delete cascade,
  type text not null default 'message',
  is_read boolean not null default false,
  created_at timestamptz not null default now()
);

-- updated_at triggers
create or replace function public.set_updated_at()
returns trigger language plpgsql as $$
begin
  new.updated_at = now();
  return new;
end $$;

drop trigger if exists trg_chat_conversations_updated on chat_conversations;
create trigger trg_chat_conversations_updated before update on chat_conversations for each row execute function public.set_updated_at();

drop trigger if exists trg_chat_participants_updated on chat_participants;
create trigger trg_chat_participants_updated before update on chat_participants for each row execute function public.set_updated_at();

drop trigger if exists trg_chat_messages_updated on chat_messages;
create trigger trg_chat_messages_updated before update on chat_messages for each row execute function public.set_updated_at();

-- On new message: update conversation last_message_at and create notifications for participants except sender
create or replace function public.chat_on_message_insert()
returns trigger language plpgsql as $$
begin
  -- update last_message_at
  update chat_conversations set last_message_at = new.created_at where id = new.conversation_id;
  -- insert notifications for all participants except sender
  insert into chat_notifications(user_id, conversation_id, message_id, type, is_read, created_at)
  select p.user_id, new.conversation_id, new.id, 'message', false, now()
  from chat_participants p
  where p.conversation_id = new.conversation_id
    and p.user_id <> new.sender_id;
  return new;
end $$;

drop trigger if exists trg_chat_messages_after_insert on chat_messages;
create trigger trg_chat_messages_after_insert
after insert on chat_messages
for each row execute function public.chat_on_message_insert();

-- RLS (simplified)
alter table chat_conversations enable row level security;
alter table chat_participants enable row level security;
alter table chat_messages enable row level security;
alter table chat_notifications enable row level security;

-- Helper to check whether a user is owner/admin of a conversation without triggering RLS recursion
create or replace function public.chat_user_is_manager(p_conversation_id uuid, p_user_id uuid)
returns boolean
language sql
security definer
set search_path = public
as $$
  select exists (
    select 1
    from chat_participants
    where conversation_id = p_conversation_id
      and user_id = p_user_id
      and role in ('owner', 'admin')
  );
$$;

revoke all on function public.chat_user_is_manager(uuid, uuid) from public;
grant execute on function public.chat_user_is_manager(uuid, uuid) to authenticated;

-- Policies: users can access conversations they participate in
create policy chat_conversations_select on chat_conversations for select to authenticated using (
  exists (select 1 from chat_participants p where p.conversation_id = chat_conversations.id and p.user_id = auth.uid())
);
create policy chat_conversations_insert on chat_conversations for insert to authenticated with check (true);
create policy chat_conversations_update on chat_conversations for update to authenticated using (
  public.chat_user_is_manager(chat_conversations.id, auth.uid())
) with check (
  public.chat_user_is_manager(chat_conversations.id, auth.uid())
);

-- Split policies for clarity: SELECT/UPDATE/DELETE cover members and admins; INSERT allows admins to add others
drop policy if exists chat_participants_all on chat_participants;
create policy chat_participants_select on chat_participants for select to authenticated using (
  user_id = auth.uid() or public.chat_user_is_manager(chat_participants.conversation_id, auth.uid())
);
create policy chat_participants_update on chat_participants for update to authenticated using (
  user_id = auth.uid() or public.chat_user_is_manager(chat_participants.conversation_id, auth.uid())
) with check (
  user_id = auth.uid() or public.chat_user_is_manager(chat_participants.conversation_id, auth.uid())
);
create policy chat_participants_delete on chat_participants for delete to authenticated using (
  user_id = auth.uid() or public.chat_user_is_manager(chat_participants.conversation_id, auth.uid())
);
create policy chat_participants_insert on chat_participants for insert to authenticated with check (
  -- allow user to add self OR owners/admins to add others
  user_id = auth.uid() or public.chat_user_is_manager(chat_participants.conversation_id, auth.uid())
);

create policy chat_messages_select on chat_messages for select to authenticated using (
  exists (select 1 from chat_participants p where p.conversation_id = chat_messages.conversation_id and p.user_id = auth.uid())
);
create policy chat_messages_insert on chat_messages for insert to authenticated with check (
  exists (select 1 from chat_participants p where p.conversation_id = chat_messages.conversation_id and p.user_id = auth.uid())
);

create policy chat_notifications_select on chat_notifications for select to authenticated using (user_id = auth.uid());
create policy chat_notifications_update on chat_notifications for update to authenticated using (user_id = auth.uid()) with check (user_id = auth.uid());

-- ---------------------------------------------------------------------------
-- Chat MQTT downlinks: queue for device/edge commands tied to conversations
-- ---------------------------------------------------------------------------

create table if not exists chat_mqtt_downlinks (
  id uuid primary key default gen_random_uuid(),
  conversation_id uuid not null references chat_conversations(id) on delete cascade,
  -- optional: specific participant user as target (用于映射设备/用户)
  target_user_id uuid references public.ak_users(id) on delete set null,
  -- 可选：显式下发表的 topic，未提供时由网关按约定推导（如 device/{user_id}/down）
  topic text,
  -- 载荷与编码：utf8/base64/json
  payload text not null,
  payload_encoding text not null default 'utf8' check (payload_encoding in ('utf8','base64','json')),
  qos smallint not null default 1 check (qos in (0,1,2)),
  retain boolean not null default false,
  -- 生命周期
  status text not null default 'pending' check (status in ('pending','sent','acked','failed','expired','canceled')),
  scheduled_at timestamptz not null default now(),
  expires_at timestamptz,
  sent_at timestamptz,
  ack_at timestamptz,
  retry_count integer not null default 0,
  last_error text,
  metadata jsonb,
  -- 关联/审计
  correlation_id uuid,
  created_by uuid not null references public.ak_users(id) on delete cascade,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);

create index if not exists idx_chat_mqtt_downlinks_conv_status_time on chat_mqtt_downlinks(conversation_id, status, scheduled_at asc);
create index if not exists idx_chat_mqtt_downlinks_creator_time on chat_mqtt_downlinks(created_by, created_at desc);
create index if not exists idx_chat_mqtt_downlinks_topic on chat_mqtt_downlinks(topic);

drop trigger if exists trg_chat_mqtt_downlinks_updated on chat_mqtt_downlinks;
create trigger trg_chat_mqtt_downlinks_updated before update on chat_mqtt_downlinks for each row execute function public.set_updated_at();

alter table chat_mqtt_downlinks enable row level security;

-- 下发表的访问控制：
-- 1) 参与该会话的用户可读取对应 conversation 的下发记录；
-- 2) 插入：必须是会话参与者，且 created_by = auth.uid()
-- 3) 更新/删除：消息创建者，或会话 owner/admin 可操作（用于重试、取消、标记 ACK 等）

create policy chat_mqtt_downlinks_select on chat_mqtt_downlinks for select to authenticated using (
  exists (
    select 1 from chat_participants p
    where p.conversation_id = chat_mqtt_downlinks.conversation_id
      and p.user_id = auth.uid()
  )
);

create policy chat_mqtt_downlinks_insert on chat_mqtt_downlinks for insert to authenticated with check (
  created_by = auth.uid() and exists (
    select 1 from chat_participants p
    where p.conversation_id = chat_mqtt_downlinks.conversation_id
      and p.user_id = auth.uid()
  )
);

create policy chat_mqtt_downlinks_update on chat_mqtt_downlinks for update to authenticated using (
  created_by = auth.uid() or exists (
    select 1 from chat_participants p
    where p.conversation_id = chat_mqtt_downlinks.conversation_id
      and p.user_id = auth.uid()
      and p.role in ('owner','admin')
  )
) with check (
  created_by = auth.uid() or exists (
    select 1 from chat_participants p
    where p.conversation_id = chat_mqtt_downlinks.conversation_id
      and p.user_id = auth.uid()
      and p.role in ('owner','admin')
  )
);

create policy chat_mqtt_downlinks_delete on chat_mqtt_downlinks for delete to authenticated using (
  created_by = auth.uid() or exists (
    select 1 from chat_participants p
    where p.conversation_id = chat_mqtt_downlinks.conversation_id
      and p.user_id = auth.uid()
      and p.role in ('owner','admin')
  )
);

-- 可选：待发送视图，供后台/报表快速查询
create or replace view chat_mqtt_downlinks_pending as
select * from chat_mqtt_downlinks where status = 'pending' and scheduled_at <= now();

-- ---------------------------------------------------------------------------
-- Gateway reporting (chat_gateway_nodes + chat_gateway_heartbeats)
-- ---------------------------------------------------------------------------

-- Nodes registry: one row per running gateway clientId
create table if not exists chat_gateway_nodes (
  id uuid primary key default gen_random_uuid(),
  name text not null,
  mqtt_client_id text not null unique,
  version text,
  region text,
  tags jsonb,
  created_at timestamptz not null default now(),
  updated_at timestamptz not null default now()
);

drop trigger if exists trg_chat_gateway_nodes_updated on chat_gateway_nodes;
create trigger trg_chat_gateway_nodes_updated
before update on chat_gateway_nodes
for each row execute function public.set_updated_at();

alter table chat_gateway_nodes enable row level security;

-- Read-all for authenticated (dashboard/ops)
create policy chat_gateway_nodes_select on chat_gateway_nodes
  for select to authenticated using (true);

-- Heartbeats: periodic metrics from gateway
create table if not exists chat_gateway_heartbeats (
  id uuid primary key default gen_random_uuid(),
  gateway_id uuid not null references chat_gateway_nodes(id) on delete cascade,
  created_at timestamptz not null default now(),
  uptime_sec integer,
  mem_rss_mb integer,
  heap_used_mb integer,
  mqtt_connected boolean,
  kafka_connected boolean,
  redis_connected boolean,
  msgs_in integer,
  msgs_out integer,
  msgs_dropped integer,
  errors integer,
  acl_denied integer,
  kafka_produced integer,
  extra jsonb
);

create index if not exists idx_chat_gateway_heartbeats_gateway_time
  on chat_gateway_heartbeats(gateway_id, created_at desc);

alter table chat_gateway_heartbeats enable row level security;

-- Read-all for authenticated (dashboard/ops)
create policy chat_gateway_heartbeats_select on chat_gateway_heartbeats
  for select to authenticated using (true);

-- Latest status per gateway (view)
create or replace view chat_gateway_status_latest as
with last as (
  select distinct on (gateway_id)
    gateway_id, id as heartbeat_id, created_at, uptime_sec, mem_rss_mb,
    heap_used_mb, mqtt_connected, kafka_connected, redis_connected,
    msgs_in, msgs_out, msgs_dropped, errors, acl_denied, kafka_produced, extra
  from chat_gateway_heartbeats
  order by gateway_id, created_at desc
)
select l.*, n.name, n.mqtt_client_id, n.version, n.region, n.tags, n.updated_at as node_updated_at
from last l
join chat_gateway_nodes n on n.id = l.gateway_id;


