-- Initialize PostgreSQL database for FreeRADIUS and RadiusController App

-- Standard FreeRADIUS PostgreSQL Schema
CREATE TABLE IF NOT EXISTS radcheck (
    id SERIAL PRIMARY KEY,
    username VARCHAR(64) NOT NULL DEFAULT '',
    attribute VARCHAR(64) NOT NULL DEFAULT '',
    op VARCHAR(2) NOT NULL DEFAULT ':=',
    value VARCHAR(253) NOT NULL DEFAULT ''
);
CREATE INDEX IF NOT EXISTS radcheck_username ON radcheck (username);

CREATE TABLE IF NOT EXISTS radreply (
    id SERIAL PRIMARY KEY,
    username VARCHAR(64) NOT NULL DEFAULT '',
    attribute VARCHAR(64) NOT NULL DEFAULT '',
    op VARCHAR(2) NOT NULL DEFAULT '=',
    value VARCHAR(253) NOT NULL DEFAULT ''
);
CREATE INDEX IF NOT EXISTS radreply_username ON radreply (username);

CREATE TABLE IF NOT EXISTS radgroupcheck (
    id SERIAL PRIMARY KEY,
    groupname VARCHAR(64) NOT NULL DEFAULT '',
    attribute VARCHAR(64) NOT NULL DEFAULT '',
    op VARCHAR(2) NOT NULL DEFAULT ':=',
    value VARCHAR(253) NOT NULL DEFAULT ''
);
CREATE INDEX IF NOT EXISTS radgroupcheck_groupname ON radgroupcheck (groupname);

CREATE TABLE IF NOT EXISTS radgroupreply (
    id SERIAL PRIMARY KEY,
    groupname VARCHAR(64) NOT NULL DEFAULT '',
    attribute VARCHAR(64) NOT NULL DEFAULT '',
    op VARCHAR(2) NOT NULL DEFAULT '=',
    value VARCHAR(253) NOT NULL DEFAULT ''
);
CREATE INDEX IF NOT EXISTS radgroupreply_groupname ON radgroupreply (groupname);

CREATE TABLE IF NOT EXISTS radusergroup (
    id SERIAL PRIMARY KEY,
    username VARCHAR(64) NOT NULL DEFAULT '',
    groupname VARCHAR(64) NOT NULL DEFAULT '',
    priority INT NOT NULL DEFAULT 1
);
CREATE INDEX IF NOT EXISTS radusergroup_username ON radusergroup (username);

CREATE TABLE IF NOT EXISTS radacct (
    radacctid BIGSERIAL PRIMARY KEY,
    acctsessionid VARCHAR(64) NOT NULL DEFAULT '',
    acctuniqueid VARCHAR(32) NOT NULL UNIQUE DEFAULT '',
    username VARCHAR(64) NOT NULL DEFAULT '',
    realm VARCHAR(64) DEFAULT '',
    nasipaddress VARCHAR(46) NOT NULL DEFAULT '',
    nasportid VARCHAR(32) DEFAULT NULL,
    nasporttype VARCHAR(32) DEFAULT NULL,
    acctstarttime TIMESTAMP WITH TIME ZONE DEFAULT NULL,
    acctupdatetime TIMESTAMP WITH TIME ZONE DEFAULT NULL,
    acctstoptime TIMESTAMP WITH TIME ZONE DEFAULT NULL,
    acctinterval INT DEFAULT NULL,
    acctsessiontime BIGINT DEFAULT NULL,
    acctauthentic VARCHAR(32) DEFAULT NULL,
    connectinfo_start VARCHAR(50) DEFAULT NULL,
    connectinfo_stop VARCHAR(50) DEFAULT NULL,
    acctinputoctets BIGINT DEFAULT NULL,
    acctoutputoctets BIGINT DEFAULT NULL,
    calledstationid VARCHAR(50) DEFAULT '',
    callingstationid VARCHAR(50) DEFAULT '',
    acctterminatecause VARCHAR(32) DEFAULT NULL,
    servicetype VARCHAR(32) DEFAULT NULL,
    framedprotocol VARCHAR(32) DEFAULT NULL,
    framedipaddress VARCHAR(46) DEFAULT '',
    framedipv6address VARCHAR(45) DEFAULT '',
    framedipv6prefix VARCHAR(45) DEFAULT '',
    framedinterfaceid VARCHAR(44) DEFAULT '',
    delegatedipv6prefix VARCHAR(45) DEFAULT '',
    class VARCHAR(64) DEFAULT NULL
);
CREATE INDEX IF NOT EXISTS radacct_username ON radacct (username);
CREATE INDEX IF NOT EXISTS radacct_active ON radacct (acctstoptime) WHERE acctstoptime IS NULL;
CREATE INDEX IF NOT EXISTS radacct_acctuniqueid ON radacct (acctuniqueid);

CREATE TABLE IF NOT EXISTS nas (
    id SERIAL PRIMARY KEY,
    nasname VARCHAR(128) NOT NULL,
    shortname VARCHAR(32),
    type VARCHAR(30) DEFAULT 'other',
    ports INT,
    secret VARCHAR(60) NOT NULL,
    server VARCHAR(64),
    community VARCHAR(50),
    description VARCHAR(200) DEFAULT 'RADIUS Client'
);

CREATE TABLE IF NOT EXISTS radpostauth (
    id BIGSERIAL PRIMARY KEY,
    username VARCHAR(64) NOT NULL DEFAULT '',
    pass VARCHAR(64) NOT NULL DEFAULT '',
    reply VARCHAR(32) NOT NULL DEFAULT '',
    authdate TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS radpostauth_username ON radpostauth (username);

-- Application Management Tables
CREATE TABLE IF NOT EXISTS guest_profiles (
    id SERIAL PRIMARY KEY,
    guest_name VARCHAR(255) NOT NULL,
    username VARCHAR(64) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
    session_time_minutes INT NOT NULL DEFAULT 60,
    data_limit_mb BIGINT NOT NULL DEFAULT 500,
    status VARCHAR(32) NOT NULL DEFAULT 'Active'
);

CREATE TABLE IF NOT EXISTS admin_users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(64) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS system_settings (
    key VARCHAR(64) PRIMARY KEY,
    value VARCHAR(255) NOT NULL,
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- Seed Default Settings
INSERT INTO system_settings (key, value) VALUES ('default_session_time', '60') ON CONFLICT (key) DO NOTHING;
INSERT INTO system_settings (key, value) VALUES ('default_data_limit', '500') ON CONFLICT (key) DO NOTHING;

-- System settings initialized on startup
-- Admin user is created by administrator during first startup via the web interface

-- Dedicated table for robust, per-user data tracking that survives AP counter resets
CREATE TABLE IF NOT EXISTS guest_data_usage (
    username VARCHAR(64) PRIMARY KEY,
    total_input_octets BIGINT NOT NULL DEFAULT 0,
    total_output_octets BIGINT NOT NULL DEFAULT 0
);

-- Trigger function to safely accumulate data usage from radacct
CREATE OR REPLACE FUNCTION update_guest_data_usage()
RETURNS TRIGGER AS $$
DECLARE
    delta_input BIGINT := 0;
    delta_output BIGINT := 0;
BEGIN
    -- Handle INSERT (new session)
    IF (TG_OP = 'INSERT') THEN
        delta_input := COALESCE(NEW.acctinputoctets, 0);
        delta_output := COALESCE(NEW.acctoutputoctets, 0);
    -- Handle UPDATE (interim updates)
    ELSIF (TG_OP = 'UPDATE') THEN
        -- Only add if the new value is greater than the old value (protects against counter resets)
        IF (COALESCE(NEW.acctinputoctets, 0) > COALESCE(OLD.acctinputoctets, 0)) THEN
            delta_input := NEW.acctinputoctets - COALESCE(OLD.acctinputoctets, 0);
        ELSIF (COALESCE(NEW.acctinputoctets, 0) < COALESCE(OLD.acctinputoctets, 0)) THEN
            -- Counter reset mid-session! Just add the new value as the delta.
            delta_input := COALESCE(NEW.acctinputoctets, 0);
        END IF;

        IF (COALESCE(NEW.acctoutputoctets, 0) > COALESCE(OLD.acctoutputoctets, 0)) THEN
            delta_output := NEW.acctoutputoctets - COALESCE(OLD.acctoutputoctets, 0);
        ELSIF (COALESCE(NEW.acctoutputoctets, 0) < COALESCE(OLD.acctoutputoctets, 0)) THEN
            -- Counter reset mid-session!
            delta_output := COALESCE(NEW.acctoutputoctets, 0);
        END IF;
    END IF;

    -- Upsert the calculated deltas into the usage table per-user
    IF (delta_input > 0 OR delta_output > 0) THEN
        INSERT INTO guest_data_usage (username, total_input_octets, total_output_octets)
        VALUES (NEW.username, delta_input, delta_output)
        ON CONFLICT (username) DO UPDATE 
        SET total_input_octets = guest_data_usage.total_input_octets + EXCLUDED.total_input_octets,
            total_output_octets = guest_data_usage.total_output_octets + EXCLUDED.total_output_octets;
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Attach trigger to radacct table
DROP TRIGGER IF EXISTS trg_update_guest_data_usage ON radacct;
CREATE TRIGGER trg_update_guest_data_usage
AFTER INSERT OR UPDATE OF acctinputoctets, acctoutputoctets ON radacct
FOR EACH ROW EXECUTE FUNCTION update_guest_data_usage();
