create extension if not exists pgcrypto;

create table if not exists public.wordle_words (
  position integer primary key,
  word text not null unique check (word ~ '^[a-z]{5}$')
);

insert into public.wordle_words (position, word) values
  (1, 'cigar'),
  (2, 'rebut'),
  (3, 'sissy'),
  (4, 'humph'),
  (5, 'awake'),
  (6, 'blush'),
  (7, 'focal'),
  (8, 'evade'),
  (9, 'naval'),
  (10, 'serve'),
  (11, 'heath'),
  (12, 'dwarf'),
  (13, 'model'),
  (14, 'karma'),
  (15, 'stink'),
  (16, 'grade'),
  (17, 'quiet'),
  (18, 'bench'),
  (19, 'abate'),
  (20, 'feign'),
  (21, 'major'),
  (22, 'death'),
  (23, 'fresh'),
  (24, 'crust'),
  (25, 'stool'),
  (26, 'colon'),
  (27, 'abase'),
  (28, 'marry'),
  (29, 'react'),
  (30, 'batty'),
  (31, 'pride'),
  (32, 'floss'),
  (33, 'helix'),
  (34, 'croak'),
  (35, 'staff'),
  (36, 'paper'),
  (37, 'unfed'),
  (38, 'whelp'),
  (39, 'trawl'),
  (40, 'outdo'),
  (41, 'adobe'),
  (42, 'crazy'),
  (43, 'sower'),
  (44, 'repay'),
  (45, 'digit'),
  (46, 'crate'),
  (47, 'cluck'),
  (48, 'spike'),
  (49, 'mimic'),
  (50, 'pound'),
  (51, 'maxim'),
  (52, 'linen'),
  (53, 'unmet'),
  (54, 'flesh'),
  (55, 'booby'),
  (56, 'forth'),
  (57, 'first'),
  (58, 'stand'),
  (59, 'belly'),
  (60, 'ivory'),
  (61, 'seedy'),
  (62, 'print'),
  (63, 'yearn'),
  (64, 'drain'),
  (65, 'bribe'),
  (66, 'stout'),
  (67, 'panel'),
  (68, 'crass'),
  (69, 'flume'),
  (70, 'offal'),
  (71, 'agree'),
  (72, 'error'),
  (73, 'swirl'),
  (74, 'argue'),
  (75, 'bleed'),
  (76, 'delta'),
  (77, 'flick'),
  (78, 'totem'),
  (79, 'wooer'),
  (80, 'front'),
  (81, 'shrub'),
  (82, 'parry'),
  (83, 'biome'),
  (84, 'lapel'),
  (85, 'start'),
  (86, 'greet'),
  (87, 'goner'),
  (88, 'golem'),
  (89, 'lusty'),
  (90, 'loopy'),
  (91, 'round'),
  (92, 'audit'),
  (93, 'lying'),
  (94, 'gamma'),
  (95, 'labor'),
  (96, 'islet'),
  (97, 'civic'),
  (98, 'forge'),
  (99, 'corny'),
  (100, 'moult'),
  (101, 'basic'),
  (102, 'salad'),
  (103, 'agate'),
  (104, 'spicy'),
  (105, 'spray'),
  (106, 'essay'),
  (107, 'fjord'),
  (108, 'spend'),
  (109, 'kebab'),
  (110, 'guild'),
  (111, 'aback'),
  (112, 'motor'),
  (113, 'alone'),
  (114, 'hatch'),
  (115, 'hyper'),
  (116, 'thumb'),
  (117, 'dowry'),
  (118, 'ought'),
  (119, 'belch'),
  (120, 'dutch')
on conflict (position) do nothing;

alter table public.wordle_words enable row level security;

create table if not exists public.wordle_rounds (
  id uuid primary key default gen_random_uuid(),
  user_id uuid not null references auth.users(id) on delete cascade,
  word text not null check (word ~ '^[a-z]{5}$'),
  hour_start timestamptz,
  started_at timestamptz not null default now(),
  next_playable_at timestamptz not null default (now() + interval '1 hour'),
  completed_at timestamptz,
  won boolean,
  guess_count integer check (guess_count between 1 and 6),
  created_at timestamptz not null default now()
);

alter table public.wordle_rounds
  add column if not exists hour_start timestamptz;

update public.wordle_rounds
set hour_start = date_trunc('hour', started_at)
where hour_start is null;

alter table public.wordle_rounds
  alter column hour_start set not null;

create index if not exists wordle_rounds_user_started_idx
  on public.wordle_rounds (user_id, started_at desc);

create unique index if not exists wordle_rounds_user_hour_idx
  on public.wordle_rounds (user_id, hour_start);

alter table public.wordle_rounds enable row level security;

drop policy if exists "Users can read their own rounds" on public.wordle_rounds;
create policy "Users can read their own rounds"
  on public.wordle_rounds
  for select
  using (auth.uid() = user_id);

drop function if exists public.start_hourly_round(text);

create or replace function public.start_hourly_round()
returns table (
  round_id uuid,
  word text,
  hour_start timestamptz,
  started_at timestamptz,
  next_playable_at timestamptz,
  completed_at timestamptz,
  won boolean,
  guess_count integer,
  server_now timestamptz,
  is_existing boolean
)
language plpgsql
security definer
set search_path = public
as $$
declare
  existing_round public.wordle_rounds%rowtype;
  new_round public.wordle_rounds%rowtype;
  current_hour timestamptz := date_trunc('hour', now());
  word_count integer;
  word_offset integer;
  hourly_word text;
begin
  if auth.uid() is null then
    raise exception 'Authentication required';
  end if;

  select count(*) into word_count from public.wordle_words;
  if word_count = 0 then
    raise exception 'No hourly words are configured';
  end if;

  word_offset := (floor(extract(epoch from current_hour) / 3600)::bigint % word_count)::integer;

  select public.wordle_words.word
    into hourly_word
  from public.wordle_words
  order by position
  limit 1 offset word_offset;

  select *
    into existing_round
  from public.wordle_rounds
  where user_id = auth.uid()
    and public.wordle_rounds.hour_start = current_hour
  order by started_at desc
  limit 1;

  if found then
    return query select
      existing_round.id,
      existing_round.word,
      existing_round.hour_start,
      existing_round.started_at,
      existing_round.next_playable_at,
      existing_round.completed_at,
      existing_round.won,
      existing_round.guess_count,
      now(),
      true;
    return;
  end if;

  insert into public.wordle_rounds (user_id, word, hour_start, next_playable_at)
  values (auth.uid(), hourly_word, current_hour, current_hour + interval '1 hour')
  returning * into new_round;

  return query select
    new_round.id,
    new_round.word,
    new_round.hour_start,
    new_round.started_at,
    new_round.next_playable_at,
    new_round.completed_at,
    new_round.won,
    new_round.guess_count,
    now(),
    false;
end;
$$;

create or replace function public.complete_hourly_round(
  round_id uuid,
  did_win boolean,
  guess_total integer
)
returns void
language plpgsql
security definer
set search_path = public
as $$
begin
  if auth.uid() is null then
    raise exception 'Authentication required';
  end if;

  if guess_total < 1 or guess_total > 6 then
    raise exception 'Guess total must be between 1 and 6';
  end if;

  update public.wordle_rounds
  set completed_at = coalesce(completed_at, now()),
      won = did_win,
      guess_count = guess_total
  where id = round_id
    and user_id = auth.uid();
end;
$$;

revoke all on public.wordle_rounds from anon, authenticated;
revoke all on public.wordle_words from anon, authenticated;
grant select on public.wordle_rounds to authenticated;
grant execute on function public.start_hourly_round() to authenticated;
grant execute on function public.complete_hourly_round(uuid, boolean, integer) to authenticated;
