CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;

CREATE TYPE team AS ENUM ('blue', 'red', 'other');

CREATE TYPE class_type AS ENUM ('scout', 'soldier', 'pyro', 'demoman', 'heavyweapons', 'engineer', 'medic', 'sniper', 'spy', 'unknown');

CREATE TYPE game_mode AS ENUM ('ultiduo', '4v4', '6v6', '7v7', '9v9', 'other');

CREATE TYPE map_type AS ENUM ('stopwatch', 'cp', 'koth', 'ctf', 'ultiduo', 'bball', 'other');

CREATE TYPE event_type AS ENUM ('charge', 'pointcap', 'medic_death', 'round_win');

CREATE TYPE medigun AS ENUM ('medigun', 'kritzkrieg', 'quickfix', 'vaccinator');

CREATE OR REPLACE FUNCTION clean_map_name(map TEXT)
    RETURNS TEXT AS $$
SELECT regexp_replace(replace(map, 'workshop/', ''), '((_(a|b|beta|u|r|v|rc|final|comptf|ugc|f)?[0-9]*[a-z]?)?(_(a|b|beta|u|r|v|rc|final|comptf|ugc|f)?[0-9]*[a-z]?(_nb[0-9]*)?)|([0-9]+[a-z]?))(\.[a-z0-9]+)?$', '', 'g');
$$ LANGUAGE SQL IMMUTABLE;

CREATE TABLE logs (
    id              INTEGER                     PRIMARY KEY,
    red_score       INTEGER                     NOT NULL,
    blue_score      INTEGER                     NOT NULL,
    length          INTEGER                     NOT NULL,
    game_mode       game_mode                   NOT NULL,
    map             TEXT                        NOT NULL,
    clean_map       TEXT GENERATED ALWAYS AS (clean_map_name(map)) STORED,
    type            map_type                    NOT NULL,
    date            TIMESTAMP WITHOUT TIME ZONE NOT NULL,
    winner          team GENERATED ALWAYS AS (CASE WHEN red_score > blue_score THEN 'red'::team WHEN blue_score > red_score THEN 'blue'::team ELSE 'other'::team END) STORED,
    version         SMALLINT                    NOT NULL,
    is_valid        BOOL GENERATED ALWAYS AS (
            length > 60 AND length < 3600 AND clean_map_name(map) != '' AND game_mode != 'other'
    ) STORED
);

CREATE INDEX logs_map_idx
    ON logs USING BTREE (map);

CREATE INDEX logs_clean_map_idx
    ON logs USING BTREE (clean_map);

CREATE INDEX logs_mode_idx
    ON logs USING BTREE (game_mode);

CREATE INDEX logs_winner_idx
    ON logs USING BTREE (winner);

CREATE INDEX logs_date_idx
    ON logs USING BTREE (date);

CREATE INDEX logs_version_idx
    ON logs USING BTREE (version);

CREATE TABLE rounds (
    id              SERIAL                      PRIMARY KEY,
    round           INTEGER                     NOT NULL,
    log_id          INTEGER                     NOT NULL REFERENCES logs(id),
    length          INTEGER                     NOT NULL,
    winner          team                        NOT NULL,
    first_cap       team                        NOT NULL,
    red_score       INTEGER                     NOT NULL,
    blue_score      INTEGER                     NOT NULL,
    red_kills       INTEGER                     NOT NULL,
    blue_kills      INTEGER                     NOT NULL,
    red_dmg         INTEGER                     NOT NULL,
    blue_dmg        INTEGER                     NOT NULL,
    red_ubers       INTEGER                     NOT NULL,
    blue_ubers      INTEGER                     NOT NULL
);

CREATE INDEX rounds_log_id_idx
    ON rounds USING BTREE (log_id);

CREATE UNIQUE INDEX rounds_round_log_id_idx
    ON rounds USING BTREE (round, log_id);

CREATE INDEX rounds_winner_idx
    ON rounds USING BTREE (winner);

CREATE INDEX rounds_first_cap_idx
    ON rounds USING BTREE (first_cap);

CREATE TABLE events_charge (
    id              BIGSERIAL                   PRIMARY KEY,
    round_id        INTEGER                     NOT NULL REFERENCES rounds(id),
    medigun         medigun                     NOT NULL,
    time            INTEGER                     NOT NULL,
    team            team                        NOT NULL,
    steam_id        BIGINT                      NOT NULL
);

CREATE INDEX events_charge_round_id_idx
    ON events_charge USING BTREE (round_id);

CREATE INDEX events_charge_steam_id_idx
    ON events_charge USING BTREE (steam_id);

CREATE TABLE events_point_cap (
    id              BIGSERIAL                   PRIMARY KEY,
    round_id        INTEGER                     NOT NULL REFERENCES rounds(id),
    time            INTEGER                     NOT NULL,
    team            team                        NOT NULL,
    point           INTEGER                     NOT NULL
);

CREATE INDEX events_point_cap_round_id_idx
    ON events_point_cap USING BTREE (round_id);

CREATE TABLE events_medic_death (
    id              BIGSERIAL                   PRIMARY KEY,
    round_id        INTEGER                     NOT NULL REFERENCES rounds(id),
    time            INTEGER                     NOT NULL,
    team            team                        NOT NULL,
    steam_id        BIGINT                      NOT NULL,
    killer          BIGINT                      NOT NULL
);

CREATE INDEX events_medic_death_round_id_idx
    ON events_medic_death USING BTREE (round_id);

CREATE INDEX events_medic_death_steam_id_idx
    ON events_medic_death USING BTREE (steam_id);

CREATE TABLE events_drop (
    id              BIGSERIAL                   PRIMARY KEY,
    round_id        INTEGER                     NOT NULL REFERENCES rounds(id),
    time            INTEGER                     NOT NULL,
    team            team                        NOT NULL,
    steam_id        BIGINT                      NOT NULL
);

CREATE INDEX events_drop_round_id_idx
    ON events_drop USING BTREE (round_id);

CREATE INDEX events_drop_steam_id_idx
    ON events_drop USING BTREE (steam_id);

CREATE TABLE events_round_win (
    id              BIGSERIAL                   PRIMARY KEY,
    round_id        INTEGER                     NOT NULL REFERENCES rounds(id),
    time            INTEGER                     NOT NULL,
    team            team                        NOT NULL
);

CREATE UNIQUE INDEX events_round_win_round_id_idx
    ON events_round_win USING BTREE (round_id);

CREATE OR REPLACE FUNCTION team_is_winner(log_id INTEGER, team team) RETURNS BOOL AS $$
DECLARE
    is_winner BOOLEAN;
BEGIN
    SELECT team = winner into is_winner FROM logs WHERE id = log_id;
    RETURN is_winner;
END; $$
    LANGUAGE PLPGSQL IMMUTABLE;

CREATE OR REPLACE FUNCTION get_game_mode(log_id INTEGER) RETURNS game_mode AS $$
DECLARE
    result game_mode;
BEGIN
    SELECT game_mode into result FROM logs WHERE id = log_id;
    RETURN result;
END; $$
    LANGUAGE PLPGSQL IMMUTABLE;

CREATE OR REPLACE FUNCTION get_clean_map(log_id INTEGER) RETURNS TEXT AS $$
DECLARE
    result TEXT;
BEGIN
    SELECT clean_map into result FROM logs WHERE id = log_id;
    RETURN result;
END; $$
    LANGUAGE PLPGSQL IMMUTABLE;

CREATE OR REPLACE FUNCTION get_date(log_id INTEGER) RETURNS TIMESTAMP WITHOUT TIME ZONE AS $$
DECLARE
    result TIMESTAMP WITHOUT TIME ZONE;
BEGIN
    SELECT date into result FROM logs WHERE id = log_id;
    RETURN result;
END; $$
    LANGUAGE PLPGSQL IMMUTABLE;

CREATE OR REPLACE FUNCTION get_length(log_id INTEGER) RETURNS INTEGER AS $$
DECLARE
    result INTEGER;
BEGIN
    SELECT length into result FROM logs WHERE id = log_id;
    RETURN result;
END; $$
    LANGUAGE PLPGSQL IMMUTABLE;

CREATE OR REPLACE FUNCTION is_log_valid(log_id INTEGER) RETURNS BOOL AS $$
DECLARE
    result BOOL;
BEGIN
    SELECT is_valid into result FROM logs WHERE id = log_id;
    RETURN result;
END; $$
    LANGUAGE PLPGSQL IMMUTABLE;

CREATE TABLE players (
    id              BIGSERIAL                   PRIMARY KEY,
    log_id          INTEGER                     NOT NULL REFERENCES logs(id),
    steam_id        BIGINT                      NOT NULL,
    name            TEXT                        NOT NULL,
    team            team                        NOT NULL,
    kills           INTEGER                     NOT NULL,
    deaths          INTEGER                     NOT NULL,
    assists         INTEGER                     NOT NULL,
    suicides        INTEGER                     NOT NULL,
    dmg             INTEGER                     NOT NULL,
    damage_taken    INTEGER                     NOT NULL,
    ubers           INTEGER                     NOT NULL,
    medigun_ubers   INTEGER                     NOT NULL,
    kritzkrieg_ubers INTEGER                    NOT NULL,
    quickfix_ubers  INTEGER                     NOT NULL,
    vaccinator_ubers INTEGER                    NOT NULL,
    drops           INTEGER                     NOT NULL,
    medkits         INTEGER                     NOT NULL,
    medkits_hp      INTEGER                     NOT NULL,
    backstabs       INTEGER                     NOT NULL,
    headshots       INTEGER                     NOT NULL,
    heal            INTEGER                     NOT NULL,
    heals_received  INTEGER                     NOT NULL,
    scout_kills     INTEGER                     NOT NULL,
    soldier_kills   INTEGER                     NOT NULL,
    pyro_kills      INTEGER                     NOT NULL,
    demoman_kills   INTEGER                     NOT NULL,
    heavy_kills     INTEGER                     NOT NULL,
    engineer_kills  INTEGER                     NOT NULL,
    medic_kills     INTEGER                     NOT NULL,
    sniper_kills    INTEGER                     NOT NULL,
    spy_kills       INTEGER                     NOT NULL,
    scout_deaths    INTEGER                     NOT NULL,
    soldier_deaths  INTEGER                     NOT NULL,
    pyro_deaths     INTEGER                     NOT NULL,
    demoman_deaths  INTEGER                     NOT NULL,
    heavy_deaths    INTEGER                     NOT NULL,
    engineer_deaths INTEGER                     NOT NULL,
    medic_deaths    INTEGER                     NOT NULL,
    sniper_deaths   INTEGER                     NOT NULL,
    spy_deaths      INTEGER                     NOT NULL,
    is_winner       BOOL GENERATED ALWAYS AS (team_is_winner(log_id, team)) STORED,
    game_mode       game_mode GENERATED ALWAYS AS (get_game_mode(log_id)) STORED,
    clean_map       TEXT GENERATED ALWAYS AS (get_clean_map(log_id)) STORED,
    date            TIMESTAMP WITHOUT TIME ZONE GENERATED ALWAYS AS (get_date(log_id)) STORED,
    length          INTEGER GENERATED ALWAYS AS (get_length(log_id)) STORED,
    is_valid        BOOL GENERATED ALWAYS AS (
        is_log_valid(log_id) AND damage_taken < 100000 AND heals_received < 100000 AND
        kills < 100 AND deaths < 100 AND get_game_mode(log_id) != 'other'
    ) STORED
);

CREATE INDEX players_log_id_idx
    ON players USING BTREE (log_id);

CREATE UNIQUE INDEX players_log_steam_id_idx
    ON players USING BTREE (log_id, steam_id);

CREATE INDEX players_steam_id_idx
    ON players USING BTREE (steam_id);

CREATE INDEX players_team_idx
    ON players USING BTREE (team);

CREATE INDEX players_is_winner_idx
    ON players USING BTREE (is_winner);

CREATE INDEX players_game_mode_idx
    ON players USING BTREE (game_mode);

CREATE INDEX players_clean_map_idx
    ON players USING BTREE (clean_map);

CREATE INDEX players_date_idx
    ON players USING BTREE (date);

CREATE INDEX players_year_idx
    ON players USING BTREE (extract(year from date));

CREATE INDEX players_is_valid_idx
    ON players USING BTREE (is_valid);

CREATE OR REPLACE FUNCTION is_player_valid(player_id BIGINT) RETURNS BOOL AS $$
DECLARE
    result BOOL;
BEGIN
    SELECT is_valid into result FROM players WHERE id = player_id;
    RETURN result;
END; $$
    LANGUAGE PLPGSQL IMMUTABLE;

CREATE TABLE class_stats (
    id              BIGSERIAL                   PRIMARY KEY,
    player_id       BIGINT                      NOT NULL REFERENCES players(id),
    type            class_type                  NOT NULL,
    time            INTEGER                     NOT NULL,
    kills           INTEGER                     NOT NULL,
    deaths          INTEGER                     NOT NULL,
    assists         INTEGER                     NOT NULL,
    dmg             INTEGER                     NOT NULL,
    is_valid        BOOL GENERATED ALWAYS AS (
        is_player_valid(player_id) AND kills < 100 AND deaths < 100 AND dmg < 50000 AND type != 'unknown' AND time > 0
    ) STORED
);

CREATE INDEX class_stats_player_id_idx
    ON class_stats USING BTREE (player_id);

CREATE UNIQUE INDEX class_stats_player_id_type_idx
    ON class_stats USING BTREE (player_id, type);

CREATE INDEX class_stats_player_id_valid_idx
    ON class_stats USING BTREE (player_id, is_valid);

CREATE TABLE player_weapon_stats (
    id              BIGSERIAL                   PRIMARY KEY,
    class_stat_id   BIGINT                      NOT NULL REFERENCES class_stats(id),
    weapon          TEXT                        NOT NULL,
    kills           INTEGER                     NOT NULL,
    shots           INTEGER                     NOT NULL,
    hits            INTEGER                     NOT NULL,
    dmg             INTEGER                     NOT NULL
);

CREATE INDEX player_weapon_stats_class_stat_id_idx
    ON player_weapon_stats USING BTREE (class_stat_id);

CREATE UNIQUE INDEX player_weapon_stats_class_stat_id_weapon_idx
    ON player_weapon_stats USING BTREE (class_stat_id, weapon);

CREATE MATERIALIZED VIEW player_stats AS
    SELECT
        game_mode, clean_map, extract(year from date)::INT as year,
            extract(month from date)::INT as month,
            class_stats.type as class,
            sum(class_stats.dmg) as damage,
            sum(class_stats.kills) as kills,
            sum(class_stats.deaths) as deaths,
            sum(class_stats.assists) as assists,
            sum(class_stats.time) as time,
            sum(players.heals_received * (length / time)) as heals_received,
            sum(players.damage_taken * (length / time)) as damage_taken,
            count(*) as count,
            sum(is_winner::INTEGER) as wins,
            steam_id
        FROM players
        INNER JOIN class_stats ON players.id = class_stats.player_id
        WHERE class_stats.is_valid
        GROUP BY game_mode, clean_map, extract(year from date)::INT, extract(month from date)::INT,
                 class_stats.type, steam_id;

CREATE INDEX player_stats_steam_id_idx
    ON player_stats USING BTREE (steam_id);

CREATE INDEX player_stats_game_mode_idx
    ON player_stats USING BTREE (game_mode);

CREATE INDEX player_stats_class_idx
    ON player_stats USING BTREE (class);

CREATE INDEX player_stats_date_idx
    ON player_stats USING BTREE (year, month);

CREATE UNIQUE INDEX player_stats_unique_idx
    ON player_stats USING BTREE (game_mode, clean_map, year, month, steam_id, class);

CREATE MATERIALIZED VIEW player_names AS
    SELECT
        steam_id, name, sum(length) as TIME, count(*) AS count
    FROM players
    GROUP BY steam_id, name;

CREATE INDEX player_names_steam_id_idx
    ON player_names USING BTREE (steam_id);

CREATE UNIQUE INDEX player_names_steam_id_name_idx
    ON player_names USING BTREE (steam_id, name);

CREATE INDEX player_names_search_idx
    ON player_names USING GIN (name gin_trgm_ops);

CREATE MATERIALIZED VIEW user_names AS
    WITH names AS
             (
                 select name, count, steam_id,
                        rank() over (partition by steam_id order by steam_id, count desc) rn
                 from player_names
             )
    SELECT steam_id, MAX(name) as name
    FROM names
    WHERE rn = 1
    GROUP BY steam_id;

CREATE UNIQUE INDEX user_names_steam_id_idx
    ON user_names USING BTREE (steam_id);

CREATE TABLE kill_streaks (
    id              BIGSERIAL                   PRIMARY KEY,
    log_id          INTEGER                     NOT NULL REFERENCES logs(id),
    steam_id        BIGINT                      NOT NULL,
    time            INTEGER                     NOT NULL,
    streak          INTEGER                     NOT NULL,
    game_mode       game_mode GENERATED ALWAYS AS (get_game_mode(log_id)) STORED,
    clean_map       TEXT GENERATED ALWAYS AS (get_clean_map(log_id)) STORED,
    is_valid        BOOL GENERATED ALWAYS AS (
        is_log_valid(log_id) AND streak < 20
    ) STORED
);

CREATE INDEX kill_streaks_id_idx
    ON kill_streaks USING BTREE (id);

CREATE INDEX kill_streaks_steam_id_idx
    ON kill_streaks USING BTREE (steam_id);

CREATE INDEX kill_streaks_log_id_idx
    ON kill_streaks USING BTREE (log_id);

CREATE INDEX kill_streaks_steam_id_streak_idx
    ON kill_streaks USING BTREE (steam_id, streak);

CREATE INDEX kill_streaks_steam_id_streak_valid_idx
    ON kill_streaks USING BTREE (steam_id, is_valid, streak);