-- =====================================================================
--  TeeCup — initielt databaseskjema  (migrasjon 001)
-- =====================================================================
--  Realiserer arkitektur-beslutningene:
--    ADR-001  tenant = organisasjon  -> organization_id på alle domenetabeller
--    ADR-002  bruker != medlemskap   -> app_user / organization_membership
--    ADR-003  shared schema + RLS    -> policy på hver domenetabell
--    ADR-005  allowance = konfig      -> session.allowance_override (jsonb)
--
--  Kjøremiljø: PostgreSQL 15+ (gen_random_uuid(), FORCE ROW LEVEL SECURITY,
--             og security_invoker-views for at RLS skal gjelde gjennom viewet).
--
--  VIKTIG DRIFTSKRAV (ADR-003):
--    Applikasjonen MÅ koble til som en rolle UTEN superuser/BYPASSRLS, og MÅ
--    sette organisasjonskonteksten per transaksjon:
--        SET LOCAL app.current_org = '<organisasjonens uuid>';
--    Uten dette returnerer RLS-policyene null rader (trygg standard: se ingenting).
-- =====================================================================

CREATE EXTENSION IF NOT EXISTS pgcrypto;   -- gen_random_uuid()
CREATE EXTENSION IF NOT EXISTS citext;     -- case-insensitiv e-post

-- ---------------------------------------------------------------------
--  Enum-typer (stabile, små mengder)
-- ---------------------------------------------------------------------
CREATE TYPE team_side          AS ENUM ('a', 'b');
CREATE TYPE hole_scope         AS ENUM ('full_18', 'front_9', 'back_9');
CREATE TYPE tournament_status  AS ENUM ('draft', 'active', 'completed', 'archived');
CREATE TYPE course_source      AS ENUM ('official', 'custom');
CREATE TYPE rating_scope       AS ENUM ('full_18', 'front_9', 'back_9');

-- Format holdes som tekst med CHECK (ikke enum), slik at nye varianter kan
-- legges til uten ALTER TYPE. Verdiene speiler Format-enumen i handicap_engine.py.
-- Selve prosenttildelingen (allowance) er konfig, ikke låst her (ADR-005).


-- =====================================================================
--  1. Identitet og tenancy  (ADR-002)
-- =====================================================================

-- Global identitet. IKKE organisasjonsavgrenset. Ingen RLS på org-nøkkel her.
CREATE TABLE app_user (
    id           uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    email        citext,                       -- ev. NULL for magic-link-only e.l.
    display_name text NOT NULL,
    created_at   timestamptz NOT NULL DEFAULT now(),
    updated_at   timestamptz NOT NULL DEFAULT now()
);

CREATE TABLE organization (
    id           uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    name         text NOT NULL,
    slug         text UNIQUE,                  -- f.eks. subdomene/URL-vennlig
    created_at   timestamptz NOT NULL DEFAULT now(),
    updated_at   timestamptz NOT NULL DEFAULT now()
);

-- Bindeleddet bruker <-> organisasjon. Én bruker kan tilhøre flere org (ADR-002).
CREATE TABLE organization_membership (
    id               uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id  uuid NOT NULL REFERENCES organization(id) ON DELETE CASCADE,
    user_id          uuid NOT NULL REFERENCES app_user(id)     ON DELETE CASCADE,
    role             text NOT NULL DEFAULT 'member'
                        CHECK (role IN ('owner', 'admin', 'member')),
    created_at       timestamptz NOT NULL DEFAULT now(),
    UNIQUE (organization_id, user_id)
);


-- =====================================================================
--  2. Personer (spiller-pool på organisasjonsnivå)
-- =====================================================================
-- En spiller er en person i organisasjonens register. Mange golfere i en
-- vennegjeng/klubb har ikke egen konto, derfor er player adskilt fra app_user;
-- user_id kobler valgfritt en spiller til en innlogget bruker.
CREATE TABLE player (
    id               uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id  uuid NOT NULL REFERENCES organization(id) ON DELETE CASCADE,
    user_id          uuid REFERENCES app_user(id) ON DELETE SET NULL,
    display_name     text NOT NULL,
    -- Gjeldende/standard handicap-indeks. Snapshottes per turnering i team_roster.
    handicap_index   numeric(4,1),
    gender           text CHECK (gender IN ('m', 'f', 'x')),  -- for kjønnsspesifikke tee-ratinger
    created_at       timestamptz NOT NULL DEFAULT now(),
    updated_at       timestamptz NOT NULL DEFAULT now(),
    UNIQUE (organization_id, id)              -- for sammensatte FK-er
);


-- =====================================================================
--  3. Baner  (ADR-004: offisielle hentes fra teeoff via API, custom lages lokalt)
-- =====================================================================
CREATE TABLE course (
    id               uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id  uuid NOT NULL REFERENCES organization(id) ON DELETE CASCADE,
    name             text NOT NULL,
    source           course_source NOT NULL DEFAULT 'custom',
    -- Referanse til offisiell bane i teeoff_db (kun når source = 'official').
    -- Ren referanse — ingen fysisk kryss-database-FK (ADR-004).
    external_course_ref text,
    created_at       timestamptz NOT NULL DEFAULT now(),
    updated_at       timestamptz NOT NULL DEFAULT now(),
    UNIQUE (organization_id, id),
    CHECK (source <> 'official' OR external_course_ref IS NOT NULL)
);

-- Hull med par og stroke index (SI). SI er normalt konstant per bane.
CREATE TABLE hole (
    id               uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id  uuid NOT NULL,
    course_id        uuid NOT NULL,
    hole_number      smallint NOT NULL CHECK (hole_number BETWEEN 1 AND 18),
    par              smallint NOT NULL CHECK (par BETWEEN 3 AND 6),
    stroke_index     smallint NOT NULL CHECK (stroke_index BETWEEN 1 AND 18),
    FOREIGN KEY (organization_id, course_id)
        REFERENCES course(organization_id, id) ON DELETE CASCADE,
    UNIQUE (course_id, hole_number),
    UNIQUE (course_id, stroke_index)          -- SI må være unik per bane
);

-- Tee (utslagssted). Rating/slope varierer per tee.
CREATE TABLE tee (
    id               uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id  uuid NOT NULL,
    course_id        uuid NOT NULL,
    name             text NOT NULL,            -- f.eks. "Gul", "Rød", "Hvit"
    gender           text CHECK (gender IN ('m', 'f', 'x')),
    created_at       timestamptz NOT NULL DEFAULT now(),
    FOREIGN KEY (organization_id, course_id)
        REFERENCES course(organization_id, id) ON DELETE CASCADE,
    UNIQUE (organization_id, id)
);

-- WHS har separate ratinger for fulle 18, front 9 og back 9. Derav egen tabell
-- slik at 9-hulls økter (front/back) bruker riktig rating (viktig for 9-hulls
-- course handicap).
CREATE TABLE tee_rating (
    id               uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id  uuid NOT NULL,
    tee_id           uuid NOT NULL,
    scope            rating_scope NOT NULL,
    course_rating    numeric(4,1) NOT NULL,
    slope_rating     smallint NOT NULL CHECK (slope_rating BETWEEN 55 AND 155),
    par              smallint NOT NULL,        -- par for det aktuelle omfanget
    FOREIGN KEY (organization_id, tee_id)
        REFERENCES tee(organization_id, id) ON DELETE CASCADE,
    UNIQUE (tee_id, scope)
);


-- =====================================================================
--  4. Turneringsstruktur
-- =====================================================================
CREATE TABLE tournament (
    id               uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id  uuid NOT NULL REFERENCES organization(id) ON DELETE CASCADE,
    name             text NOT NULL,
    status           tournament_status NOT NULL DEFAULT 'draft',
    start_date       date,
    created_by       uuid REFERENCES app_user(id) ON DELETE SET NULL,
    created_at       timestamptz NOT NULL DEFAULT now(),
    updated_at       timestamptz NOT NULL DEFAULT now(),
    UNIQUE (organization_id, id)
);

-- Lag i en turnering. Ryder Cup = 2 lag, men modellen låser ikke antallet.
CREATE TABLE team (
    id               uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id  uuid NOT NULL,
    tournament_id    uuid NOT NULL,
    name             text NOT NULL,            -- f.eks. "Team Europa"
    color            text,                     -- for UI
    created_at       timestamptz NOT NULL DEFAULT now(),
    FOREIGN KEY (organization_id, tournament_id)
        REFERENCES tournament(organization_id, id) ON DELETE CASCADE,
    UNIQUE (organization_id, id),
    UNIQUE (tournament_id, name)
);

-- SPILLER-POOL: alle spillere som er tatt ut på et lag for turneringen.
-- Reserver er ganske enkelt rader her som ikke får en match_participant i en
-- gitt økt. handicap_index_snapshot fryser indeksen for reproduserbare resultater.
CREATE TABLE team_roster (
    id                     uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id        uuid NOT NULL,
    team_id                uuid NOT NULL,
    player_id              uuid NOT NULL,
    handicap_index_snapshot numeric(4,1),      -- fryst ved uttak
    is_captain             boolean NOT NULL DEFAULT false,
    created_at             timestamptz NOT NULL DEFAULT now(),
    FOREIGN KEY (organization_id, team_id)
        REFERENCES team(organization_id, id) ON DELETE CASCADE,
    FOREIGN KEY (organization_id, player_id)
        REFERENCES player(organization_id, id) ON DELETE RESTRICT,
    UNIQUE (organization_id, id),
    UNIQUE (team_id, player_id)                -- en spiller én gang per lag
);

-- ØKT (session): ett format, ett hullomfang, én allowance-konfig. En turnering
-- er en ORDNET sekvens av økter (foursome -> fourball -> singles ...).
CREATE TABLE session (
    id               uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id  uuid NOT NULL,
    tournament_id    uuid NOT NULL,
    sequence         smallint NOT NULL,        -- rekkefølge i turneringen (1,2,3...)
    name             text,                     -- f.eks. "Lørdag formiddag"
    format           text NOT NULL CHECK (format IN
                        ('singles','fourball','foursome','greensome','scramble_2','scramble_4')),
    hole_config      hole_scope NOT NULL DEFAULT 'full_18',
    course_id        uuid NOT NULL,
    points_per_match numeric(3,1) NOT NULL DEFAULT 1.0,
    -- ADR-005: NULL => bruk motorens standard-allowance for formatet.
    -- Ellers en overstyring, f.eks. {"type":"per_player","percentage":0.85}.
    allowance_override jsonb,
    created_at       timestamptz NOT NULL DEFAULT now(),
    updated_at       timestamptz NOT NULL DEFAULT now(),
    FOREIGN KEY (organization_id, tournament_id)
        REFERENCES tournament(organization_id, id) ON DELETE CASCADE,
    FOREIGN KEY (organization_id, course_id)
        REFERENCES course(organization_id, id) ON DELETE RESTRICT,
    UNIQUE (organization_id, id),
    UNIQUE (tournament_id, sequence)
);

-- MATCH: én kamp i en økt, side a mot side b. Poeng caches når resultatet er
-- avgjort (beregnes av handicap_engine; SQL summerer bare de cachede poengene).
CREATE TABLE match (
    id               uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id  uuid NOT NULL,
    session_id       uuid NOT NULL,
    sequence         smallint NOT NULL,        -- kamp-nr innen økten
    team_a_id        uuid NOT NULL,            -- hvilket lag er side a
    team_b_id        uuid NOT NULL,            -- hvilket lag er side b
    -- Cachede resultatfelt (kilde: motoren). NULL = ikke avgjort ennå.
    status_text      text,                     -- f.eks. "3&2 (A)", "AS"
    points_side_a    numeric(3,1),
    points_side_b    numeric(3,1),
    created_at       timestamptz NOT NULL DEFAULT now(),
    updated_at       timestamptz NOT NULL DEFAULT now(),
    FOREIGN KEY (organization_id, session_id)
        REFERENCES session(organization_id, id) ON DELETE CASCADE,
    FOREIGN KEY (organization_id, team_a_id)
        REFERENCES team(organization_id, id) ON DELETE RESTRICT,
    FOREIGN KEY (organization_id, team_b_id)
        REFERENCES team(organization_id, id) ON DELETE RESTRICT,
    UNIQUE (organization_id, id),
    UNIQUE (session_id, sequence),
    CHECK (team_a_id <> team_b_id)
);

-- Hvilke spillere fra poolen som faktisk spiller matchen, og på hvilken side.
-- 1 deltaker per side i singles, 2 i foursome/fourball/greensome, N i scramble.
-- (Antallet vs. format valideres i applikasjonslaget.)
CREATE TABLE match_participant (
    id               uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id  uuid NOT NULL,
    match_id         uuid NOT NULL,
    team_side        team_side NOT NULL,
    team_roster_id   uuid NOT NULL,            -- peker inn i poolen
    tee_id           uuid NOT NULL,            -- tee spilleren bruker (mixed tees støttes)
    -- Cachede handicap-felt (kilde: motoren, ut fra indeks-snapshot + tee + allowance).
    course_handicap  smallint,
    playing_handicap smallint,
    created_at       timestamptz NOT NULL DEFAULT now(),
    FOREIGN KEY (organization_id, match_id)
        REFERENCES match(organization_id, id) ON DELETE CASCADE,
    FOREIGN KEY (organization_id, team_roster_id)
        REFERENCES team_roster(organization_id, id) ON DELETE RESTRICT,
    FOREIGN KEY (organization_id, tee_id)
        REFERENCES tee(organization_id, id) ON DELETE RESTRICT,
    UNIQUE (organization_id, id),
    UNIQUE (match_id, team_roster_id)          -- en spiller kan ikke spille to plasser i samme match
);


-- =====================================================================
--  5. Score (kilde-sannhet: brutto slag per hull)
-- =====================================================================
-- Netto beregnes av motoren (brutto - tildelte slag på hullet). Vi lagrer ikke
-- netto; det er avledet. Matchresultat caches på match-raden.
--
-- To scoringsmoduser i samme tabell:
--   * Individuell ball (singles, fourball): match_participant_id satt.
--   * Delt ball  (foursome, greensome, scramble): match_participant_id = NULL,
--     scoren tilhører HELE siden (team_side).
-- Hvilken modus som gjelder styres av øktens format (håndheves i app-laget).
CREATE TABLE hole_score (
    id                   uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    organization_id      uuid NOT NULL,
    match_id             uuid NOT NULL,
    team_side            team_side NOT NULL,
    match_participant_id uuid,                 -- NULL for delt-ball-formater
    hole_number          smallint NOT NULL CHECK (hole_number BETWEEN 1 AND 18),
    gross_strokes        smallint NOT NULL CHECK (gross_strokes BETWEEN 1 AND 20),
    created_at           timestamptz NOT NULL DEFAULT now(),
    updated_at           timestamptz NOT NULL DEFAULT now(),
    FOREIGN KEY (organization_id, match_id)
        REFERENCES match(organization_id, id) ON DELETE CASCADE,
    FOREIGN KEY (organization_id, match_participant_id)
        REFERENCES match_participant(organization_id, id) ON DELETE CASCADE
);

-- Unik score per spiller per hull (individuell ball) ...
CREATE UNIQUE INDEX hole_score_player_unique
    ON hole_score (match_participant_id, hole_number)
    WHERE match_participant_id IS NOT NULL;

-- ... og unik score per side per hull (delt ball).
CREATE UNIQUE INDEX hole_score_side_unique
    ON hole_score (match_id, team_side, hole_number)
    WHERE match_participant_id IS NULL;


-- =====================================================================
--  6. Indekser  (RLS-predikat og FK-oppslag; Postgres indekserer ikke FK automatisk)
-- =====================================================================
CREATE INDEX ON player            (organization_id);
CREATE INDEX ON course            (organization_id);
CREATE INDEX ON hole              (organization_id, course_id);
CREATE INDEX ON tee               (organization_id, course_id);
CREATE INDEX ON tee_rating        (organization_id, tee_id);
CREATE INDEX ON tournament        (organization_id);
CREATE INDEX ON team              (organization_id, tournament_id);
CREATE INDEX ON team_roster       (organization_id, team_id);
CREATE INDEX ON team_roster       (organization_id, player_id);
CREATE INDEX ON session           (organization_id, tournament_id);
CREATE INDEX ON match             (organization_id, session_id);
CREATE INDEX ON match_participant (organization_id, match_id);
CREATE INDEX ON hole_score        (organization_id, match_id);


-- =====================================================================
--  7. Row-Level Security  (ADR-003)
-- =====================================================================
-- Standard org-isolasjonspolicy på hver domenetabell. Konteksten settes av
-- applikasjonen via:  SET LOCAL app.current_org = '<uuid>';
-- current_setting(..., true) gir NULL (ikke feil) når konteksten mangler ->
-- policyen slipper da ingen rader gjennom (trygg standard).
DO $$
DECLARE t text;
BEGIN
    FOREACH t IN ARRAY ARRAY[
        'player','course','hole','tee','tee_rating',
        'tournament','team','team_roster','session',
        'match','match_participant','hole_score'
    ]
    LOOP
        EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY;', t);
        EXECUTE format('ALTER TABLE %I FORCE  ROW LEVEL SECURITY;', t);
        EXECUTE format($p$
            CREATE POLICY org_isolation ON %I
                USING       (organization_id = current_setting('app.current_org', true)::uuid)
                WITH CHECK  (organization_id = current_setting('app.current_org', true)::uuid);
        $p$, t);
    END LOOP;
END $$;

-- Organisasjonsraden selv: synlig kun for den aktive konteksten.
ALTER TABLE organization ENABLE ROW LEVEL SECURITY;
ALTER TABLE organization FORCE  ROW LEVEL SECURITY;
CREATE POLICY org_self ON organization
    USING (id = current_setting('app.current_org', true)::uuid);

-- MERK: app_user og organization_membership håndteres av auth-laget (en bruker
-- må kunne se sine EGNE medlemskap for å velge organisasjon). Egne policyer for
-- disse defineres sammen med autentiseringsdesignet, ikke her.


-- =====================================================================
--  8. Leaderboard-view  (SQL summerer cachede matchpoeng; motoren beregner dem)
-- =====================================================================
-- security_invoker => viewet kjører med den spørrende rollens RLS, ikke eierens.
-- Uten dette kunne standings lekke matcher på tvers av organisasjoner.
CREATE VIEW tournament_standings
    WITH (security_invoker = true) AS
SELECT
    t.organization_id,
    t.tournament_id,
    t.id                       AS team_id,
    t.name                     AS team_name,
    COALESCE(SUM(
        CASE WHEN m.team_a_id = t.id THEN m.points_side_a
             WHEN m.team_b_id = t.id THEN m.points_side_b
             ELSE 0 END
    ), 0)                      AS points
FROM team t
LEFT JOIN match m
       ON m.organization_id = t.organization_id
      AND (m.team_a_id = t.id OR m.team_b_id = t.id)
GROUP BY t.organization_id, t.tournament_id, t.id, t.name;


-- =====================================================================
--  9. updated_at-trigger (valgfritt mønster — vist på to tabeller)
-- =====================================================================
CREATE OR REPLACE FUNCTION set_updated_at() RETURNS trigger AS $$
BEGIN
    NEW.updated_at := now();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_tournament_updated
    BEFORE UPDATE ON tournament
    FOR EACH ROW EXECUTE FUNCTION set_updated_at();

CREATE TRIGGER trg_match_updated
    BEFORE UPDATE ON match
    FOR EACH ROW EXECUTE FUNCTION set_updated_at();
-- (Samme mønster kan legges på øvrige tabeller med updated_at.)
