-- Panel hosting control panel — initial schema

CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "citext";

-- ─── Enums ───────────────────────────────────────────────────────────────────

CREATE TYPE user_status AS ENUM ('active', 'suspended', 'pending');
CREATE TYPE user_role AS ENUM ('superadmin', 'admin', 'reseller', 'user');

CREATE TYPE server_status AS ENUM ('online', 'offline', 'maintenance', 'provisioning');
CREATE TYPE site_status AS ENUM ('active', 'suspended', 'creating', 'deleting', 'error');
CREATE TYPE ssl_type AS ENUM ('letsencrypt', 'custom', 'self_signed');
CREATE TYPE ssl_status AS ENUM ('pending', 'active', 'expired', 'error');
CREATE TYPE db_engine AS ENUM ('mysql', 'postgresql');
CREATE TYPE backup_type AS ENUM ('full', 'files', 'database');
CREATE TYPE backup_status AS ENUM ('pending', 'running', 'completed', 'failed');
CREATE TYPE job_status AS ENUM ('pending', 'running', 'completed', 'failed', 'cancelled');
CREATE TYPE dns_record_type AS ENUM ('A', 'AAAA', 'CNAME', 'MX', 'TXT', 'NS', 'SRV', 'PTR');

-- ─── Users & auth ────────────────────────────────────────────────────────────

CREATE TABLE users (
    id          BIGSERIAL PRIMARY KEY,
    uuid        UUID NOT NULL DEFAULT gen_random_uuid() UNIQUE,
    email       CITEXT NOT NULL UNIQUE,
    username    VARCHAR(64) NOT NULL UNIQUE,
    password_hash TEXT NOT NULL,
    role        user_role NOT NULL DEFAULT 'user',
    status      user_status NOT NULL DEFAULT 'pending',
    locale      VARCHAR(10) NOT NULL DEFAULT 'ru',
    timezone    VARCHAR(64) NOT NULL DEFAULT 'Europe/Moscow',
    last_login_at TIMESTAMPTZ,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE sessions (
    id          BIGSERIAL PRIMARY KEY,
    user_id     BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    token_hash  TEXT NOT NULL UNIQUE,
    ip_address  INET,
    user_agent  TEXT,
    expires_at  TIMESTAMPTZ NOT NULL,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_sessions_user_id ON sessions(user_id);
CREATE INDEX idx_sessions_expires_at ON sessions(expires_at);

CREATE TABLE api_keys (
    id          BIGSERIAL PRIMARY KEY,
    user_id     BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    name        VARCHAR(128) NOT NULL,
    key_hash    TEXT NOT NULL UNIQUE,
    key_prefix  VARCHAR(16) NOT NULL,
    last_used_at TIMESTAMPTZ,
    expires_at  TIMESTAMPTZ,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_api_keys_user_id ON api_keys(user_id);

-- ─── Servers (managed nodes) ─────────────────────────────────────────────────

CREATE TABLE servers (
    id              BIGSERIAL PRIMARY KEY,
    uuid            UUID NOT NULL DEFAULT gen_random_uuid() UNIQUE,
    name            VARCHAR(128) NOT NULL,
    hostname        VARCHAR(255) NOT NULL,
    ip_address      INET NOT NULL,
    ssh_port        INT NOT NULL DEFAULT 22,
    status          server_status NOT NULL DEFAULT 'provisioning',
    agent_token_hash TEXT,
    agent_version   VARCHAR(32),
    os_info         JSONB NOT NULL DEFAULT '{}',
    resources       JSONB NOT NULL DEFAULT '{}',
    settings        JSONB NOT NULL DEFAULT '{}',
    last_seen_at    TIMESTAMPTZ,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE UNIQUE INDEX idx_servers_hostname ON servers(hostname);

-- User access to servers (multi-tenant / reseller model)
CREATE TABLE server_access (
    user_id     BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    server_id   BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
    can_manage  BOOLEAN NOT NULL DEFAULT false,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (user_id, server_id)
);

-- ─── Sites (websites) ────────────────────────────────────────────────────────

CREATE TABLE sites (
    id              BIGSERIAL PRIMARY KEY,
    uuid            UUID NOT NULL DEFAULT gen_random_uuid() UNIQUE,
    server_id       BIGINT NOT NULL REFERENCES servers(id) ON DELETE RESTRICT,
    owner_id        BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
    name            VARCHAR(128) NOT NULL,
    document_root   VARCHAR(512) NOT NULL,
    php_version     VARCHAR(16),
    status          site_status NOT NULL DEFAULT 'creating',
    settings        JSONB NOT NULL DEFAULT '{}',
    disk_quota_mb   INT,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (server_id, name)
);

CREATE INDEX idx_sites_owner_id ON sites(owner_id);
CREATE INDEX idx_sites_server_id ON sites(server_id);

CREATE TABLE domains (
    id          BIGSERIAL PRIMARY KEY,
    site_id     BIGINT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
    domain      CITEXT NOT NULL,
    is_primary  BOOLEAN NOT NULL DEFAULT false,
    ssl_enabled BOOLEAN NOT NULL DEFAULT true,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (domain)
);

CREATE INDEX idx_domains_site_id ON domains(site_id);

-- Only one primary domain per site
CREATE UNIQUE INDEX idx_domains_site_primary
    ON domains(site_id) WHERE is_primary = true;

-- ─── Databases ───────────────────────────────────────────────────────────────

CREATE TABLE databases (
    id          BIGSERIAL PRIMARY KEY,
    uuid        UUID NOT NULL DEFAULT gen_random_uuid() UNIQUE,
    site_id     BIGINT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
    server_id   BIGINT NOT NULL REFERENCES servers(id) ON DELETE RESTRICT,
    name        VARCHAR(64) NOT NULL,
    engine      db_engine NOT NULL DEFAULT 'mysql',
    charset     VARCHAR(32) NOT NULL DEFAULT 'utf8mb4',
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (server_id, engine, name)
);

CREATE INDEX idx_databases_site_id ON databases(site_id);

CREATE TABLE database_users (
    id              BIGSERIAL PRIMARY KEY,
    database_id     BIGINT NOT NULL REFERENCES databases(id) ON DELETE CASCADE,
    username        VARCHAR(64) NOT NULL,
    password_hash   TEXT NOT NULL,
    privileges      TEXT[] NOT NULL DEFAULT '{ALL}',
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (database_id, username)
);

-- ─── FTP ─────────────────────────────────────────────────────────────────────

CREATE TABLE ftp_accounts (
    id              BIGSERIAL PRIMARY KEY,
    site_id         BIGINT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
    username        VARCHAR(64) NOT NULL,
    password_hash   TEXT NOT NULL,
    home_path       VARCHAR(512) NOT NULL,
    quota_mb        INT,
    is_active       BOOLEAN NOT NULL DEFAULT true,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (site_id, username)
);

-- ─── SSL certificates ────────────────────────────────────────────────────────

CREATE TABLE ssl_certificates (
    id          BIGSERIAL PRIMARY KEY,
    domain_id   BIGINT NOT NULL REFERENCES domains(id) ON DELETE CASCADE,
    type        ssl_type NOT NULL,
    status      ssl_status NOT NULL DEFAULT 'pending',
    issuer      VARCHAR(255),
    cert_pem    TEXT,
    key_pem     TEXT,
    chain_pem   TEXT,
    issued_at   TIMESTAMPTZ,
    expires_at  TIMESTAMPTZ,
    auto_renew  BOOLEAN NOT NULL DEFAULT true,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_ssl_certificates_domain_id ON ssl_certificates(domain_id);
CREATE INDEX idx_ssl_certificates_expires_at ON ssl_certificates(expires_at);

-- ─── Mail ────────────────────────────────────────────────────────────────────

CREATE TABLE mail_domains (
    id          BIGSERIAL PRIMARY KEY,
    site_id     BIGINT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
    domain      CITEXT NOT NULL UNIQUE,
    dkim_enabled BOOLEAN NOT NULL DEFAULT true,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE mail_accounts (
    id              BIGSERIAL PRIMARY KEY,
    mail_domain_id  BIGINT NOT NULL REFERENCES mail_domains(id) ON DELETE CASCADE,
    email           CITEXT NOT NULL UNIQUE,
    password_hash   TEXT NOT NULL,
    quota_mb        INT NOT NULL DEFAULT 1024,
    is_active       BOOLEAN NOT NULL DEFAULT true,
    created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at      TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_mail_accounts_mail_domain_id ON mail_accounts(mail_domain_id);

-- ─── DNS ─────────────────────────────────────────────────────────────────────

CREATE TABLE dns_zones (
    id          BIGSERIAL PRIMARY KEY,
    server_id   BIGINT NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
    domain      CITEXT NOT NULL,
    serial      BIGINT NOT NULL DEFAULT 1,
    ttl         INT NOT NULL DEFAULT 3600,
    is_active   BOOLEAN NOT NULL DEFAULT true,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE (server_id, domain)
);

CREATE TABLE dns_records (
    id          BIGSERIAL PRIMARY KEY,
    zone_id     BIGINT NOT NULL REFERENCES dns_zones(id) ON DELETE CASCADE,
    type        dns_record_type NOT NULL,
    name        VARCHAR(255) NOT NULL DEFAULT '@',
    content     TEXT NOT NULL,
    ttl         INT NOT NULL DEFAULT 3600,
    priority    INT,
    is_active   BOOLEAN NOT NULL DEFAULT true,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_dns_records_zone_id ON dns_records(zone_id);

-- ─── Cron jobs ───────────────────────────────────────────────────────────────

CREATE TABLE cron_jobs (
    id          BIGSERIAL PRIMARY KEY,
    site_id     BIGINT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
    schedule    VARCHAR(128) NOT NULL,
    command     TEXT NOT NULL,
    run_as      VARCHAR(64) NOT NULL DEFAULT 'www-data',
    is_active   BOOLEAN NOT NULL DEFAULT true,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_cron_jobs_site_id ON cron_jobs(site_id);

-- ─── Backups ─────────────────────────────────────────────────────────────────

CREATE TABLE backups (
    id          BIGSERIAL PRIMARY KEY,
    uuid        UUID NOT NULL DEFAULT gen_random_uuid() UNIQUE,
    site_id     BIGINT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
    type        backup_type NOT NULL,
    status      backup_status NOT NULL DEFAULT 'pending',
    storage_path VARCHAR(1024),
    size_bytes  BIGINT,
    error_message TEXT,
    started_at  TIMESTAMPTZ,
    completed_at TIMESTAMPTZ,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_backups_site_id ON backups(site_id);
CREATE INDEX idx_backups_status ON backups(status);

-- ─── Async job queue (panel → agent tasks) ───────────────────────────────────

CREATE TABLE jobs (
    id          BIGSERIAL PRIMARY KEY,
    uuid        UUID NOT NULL DEFAULT gen_random_uuid() UNIQUE,
    server_id   BIGINT REFERENCES servers(id) ON DELETE SET NULL,
    site_id     BIGINT REFERENCES sites(id) ON DELETE SET NULL,
    user_id     BIGINT REFERENCES users(id) ON DELETE SET NULL,
    type        VARCHAR(64) NOT NULL,
    payload     JSONB NOT NULL DEFAULT '{}',
    status      job_status NOT NULL DEFAULT 'pending',
    result      JSONB,
    error_message TEXT,
    attempts    INT NOT NULL DEFAULT 0,
    max_attempts INT NOT NULL DEFAULT 3,
    scheduled_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    started_at  TIMESTAMPTZ,
    completed_at TIMESTAMPTZ,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_jobs_status_scheduled ON jobs(status, scheduled_at)
    WHERE status IN ('pending', 'running');
CREATE INDEX idx_jobs_server_id ON jobs(server_id);

-- ─── Audit log ───────────────────────────────────────────────────────────────

CREATE TABLE audit_logs (
    id          BIGSERIAL PRIMARY KEY,
    user_id     BIGINT REFERENCES users(id) ON DELETE SET NULL,
    action      VARCHAR(64) NOT NULL,
    resource    VARCHAR(64) NOT NULL,
    resource_id BIGINT,
    details     JSONB NOT NULL DEFAULT '{}',
    ip_address  INET,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_audit_logs_user_id ON audit_logs(user_id);
CREATE INDEX idx_audit_logs_resource ON audit_logs(resource, resource_id);
CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at);

-- ─── updated_at trigger ──────────────────────────────────────────────────────

CREATE OR REPLACE FUNCTION set_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = now();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_users_updated_at
    BEFORE UPDATE ON users FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

CREATE TRIGGER trg_servers_updated_at
    BEFORE UPDATE ON servers FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

CREATE TRIGGER trg_sites_updated_at
    BEFORE UPDATE ON sites FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

CREATE TRIGGER trg_ftp_accounts_updated_at
    BEFORE UPDATE ON ftp_accounts FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

CREATE TRIGGER trg_ssl_certificates_updated_at
    BEFORE UPDATE ON ssl_certificates FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

CREATE TRIGGER trg_mail_accounts_updated_at
    BEFORE UPDATE ON mail_accounts FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

CREATE TRIGGER trg_dns_zones_updated_at
    BEFORE UPDATE ON dns_zones FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

CREATE TRIGGER trg_dns_records_updated_at
    BEFORE UPDATE ON dns_records FOR EACH ROW EXECUTE PROCEDURE set_updated_at();

CREATE TRIGGER trg_cron_jobs_updated_at
    BEFORE UPDATE ON cron_jobs FOR EACH ROW EXECUTE PROCEDURE set_updated_at();
