CREATE TABLE IF NOT EXISTS applications (
    id            BIGSERIAL PRIMARY KEY,
    app_code      VARCHAR(64) UNIQUE NOT NULL,
    name          VARCHAR(128) NOT NULL,
    department    VARCHAR(128) NOT NULL DEFAULT '',
    owner         VARCHAR(64) NOT NULL DEFAULT '',
    description   TEXT NOT NULL DEFAULT '',
    created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE IF NOT EXISTS environments (
    id            BIGSERIAL PRIMARY KEY,
    env_code      VARCHAR(32) UNIQUE NOT NULL,
    name          VARCHAR(64) NOT NULL,
    sort_order    INT NOT NULL DEFAULT 0,
    description   TEXT NOT NULL DEFAULT ''
);

CREATE TABLE IF NOT EXISTS namespaces (
    id            BIGSERIAL PRIMARY KEY,
    app_id        BIGINT NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
    name          VARCHAR(64) NOT NULL,
    format        VARCHAR(16) NOT NULL DEFAULT 'properties',
    description   TEXT NOT NULL DEFAULT '',
    UNIQUE(app_id, name),
    CONSTRAINT namespaces_format_check CHECK (format IN ('properties', 'yaml', 'json', 'xml'))
);

CREATE TABLE IF NOT EXISTS config_items (
    id             BIGSERIAL PRIMARY KEY,
    namespace_id   BIGINT NOT NULL REFERENCES namespaces(id) ON DELETE CASCADE,
    env_id         BIGINT NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
    key            VARCHAR(256) NOT NULL,
    value          TEXT NOT NULL,
    released_value TEXT,
    pending_delete BOOLEAN NOT NULL DEFAULT false,
    comment        TEXT NOT NULL DEFAULT '',
    updated_by     VARCHAR(64) NOT NULL DEFAULT 'admin',
    updated_at     TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE(namespace_id, env_id, key)
);

CREATE TABLE IF NOT EXISTS releases (
    id            BIGSERIAL PRIMARY KEY,
    namespace_id  BIGINT NOT NULL REFERENCES namespaces(id) ON DELETE CASCADE,
    env_id        BIGINT NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
    version       INT NOT NULL,
    snapshot      JSONB NOT NULL,
    diff_added    INT NOT NULL DEFAULT 0,
    diff_modified INT NOT NULL DEFAULT 0,
    diff_removed  INT NOT NULL DEFAULT 0,
    comment       TEXT NOT NULL DEFAULT '',
    operator      VARCHAR(64) NOT NULL,
    etcd_revision BIGINT,
    status        VARCHAR(16) NOT NULL DEFAULT 'pending',
    created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
    UNIQUE(namespace_id, env_id, version),
    CONSTRAINT releases_status_check CHECK (status IN ('pending', 'applied', 'failed'))
);

CREATE TABLE IF NOT EXISTS release_outbox (
    id              BIGSERIAL PRIMARY KEY,
    release_id      BIGINT NOT NULL REFERENCES releases(id) ON DELETE CASCADE,
    etcd_key         VARCHAR(512) NOT NULL,
    payload          JSONB NOT NULL,
    status           VARCHAR(16) NOT NULL DEFAULT 'pending',
    retry_count      INT NOT NULL DEFAULT 0,
    last_error       TEXT NOT NULL DEFAULT '',
    next_attempt_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
    locked_at        TIMESTAMPTZ,
    created_at       TIMESTAMPTZ NOT NULL DEFAULT now(),
    CONSTRAINT outbox_status_check CHECK (status IN ('pending', 'processing', 'done', 'failed'))
);

CREATE INDEX IF NOT EXISTS release_outbox_poll_idx
    ON release_outbox(status, next_attempt_at, id);
CREATE INDEX IF NOT EXISTS releases_scope_idx
    ON releases(namespace_id, env_id, version DESC);
CREATE INDEX IF NOT EXISTS config_items_scope_idx
    ON config_items(namespace_id, env_id, key);

CREATE TABLE IF NOT EXISTS users (
    id            BIGSERIAL PRIMARY KEY,
    username      VARCHAR(64) UNIQUE NOT NULL,
    password_hash VARCHAR(256) NOT NULL,
    display_name  VARCHAR(64) NOT NULL DEFAULT '',
    created_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE IF NOT EXISTS roles (
    id   BIGSERIAL PRIMARY KEY,
    name VARCHAR(32) UNIQUE NOT NULL
);

CREATE TABLE IF NOT EXISTS user_app_roles (
    user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    app_id  BIGINT NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
    role_id BIGINT NOT NULL REFERENCES roles(id) ON DELETE CASCADE,
    PRIMARY KEY (user_id, app_id)
);

CREATE TABLE IF NOT EXISTS audit_logs (
    id           BIGSERIAL PRIMARY KEY,
    actor        VARCHAR(64) NOT NULL,
    action       VARCHAR(32) NOT NULL,
    target_type  VARCHAR(32) NOT NULL,
    target_id    BIGINT,
    detail       JSONB,
    created_at   TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX IF NOT EXISTS audit_logs_created_idx ON audit_logs(created_at DESC);

CREATE TABLE IF NOT EXISTS gray_rules (
    id            BIGSERIAL PRIMARY KEY,
    namespace_id  BIGINT NOT NULL REFERENCES namespaces(id) ON DELETE CASCADE,
    env_id        BIGINT NOT NULL REFERENCES environments(id) ON DELETE CASCADE,
    rule_type     VARCHAR(16) NOT NULL,
    rule_value    JSONB NOT NULL,
    overrides     JSONB NOT NULL,
    enabled       BOOLEAN NOT NULL DEFAULT true,
    created_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);

INSERT INTO environments(env_code, name, sort_order, description) VALUES
    ('DEV', '开发环境', 1, '开发联调使用'),
    ('TEST', '测试环境', 2, 'QA 测试验证'),
    ('STAGING', '预发环境', 3, '上线前灰度验证'),
    ('PROD', '生产环境', 4, '线上正式环境')
ON CONFLICT (env_code) DO NOTHING;

INSERT INTO roles(name) VALUES ('admin'), ('app-owner'), ('viewer')
ON CONFLICT (name) DO NOTHING;
