-- ╔══════════════════════════════════════════════════════════════════╗
-- ║ CryptoWallet API — Production DB schema (idempotent, custodial)  ║
-- ║ Применять: psql -h <host> -U postgres_user -d postgres -f ...     ║
-- ║ Безопасно прогонять повторно на existing БД.                      ║
-- ╚══════════════════════════════════════════════════════════════════╝

CREATE TABLE IF NOT EXISTS users (
    id                  VARCHAR(26)               NOT NULL PRIMARY KEY,
    email               VARCHAR(255)              NOT NULL UNIQUE,
    password_hash       VARCHAR(255)              NOT NULL,
    last_name           VARCHAR(128),
    first_name          VARCHAR(128),
    middle_name         VARCHAR(128),
    birth_date          DATE,
    -- DEPRECATED: исторически generic crypto address; для ETH используем erc20 ниже.
    crypto_wallet       VARCHAR(255),
    phone               VARCHAR(16),
    inn                 VARCHAR(12),
    kyc_verified        BOOLEAN                   NOT NULL DEFAULT FALSE,
    kyc_verified_at     TIMESTAMP WITH TIME ZONE,
    is_deleted          BOOLEAN                   NOT NULL DEFAULT FALSE,
    created_at          TIMESTAMP WITH TIME ZONE  NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at          TIMESTAMP WITH TIME ZONE  NOT NULL DEFAULT CURRENT_TIMESTAMP,
    passport_data       VARCHAR(255),
    erc20               VARCHAR(255),
    -- EXTENSION (custodial wallet support):
    -- AES-256-GCM blob: IV(12) || ciphertext || authTag(16), base64. Master-key в Vault.
    encrypted_mnemonic  TEXT
);

-- Idempotent ALTERs для existing БД без extension-columns
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'encrypted_mnemonic') THEN
        ALTER TABLE users ADD COLUMN encrypted_mnemonic TEXT;
    END IF;
    IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'erc20') THEN
        ALTER TABLE users ADD COLUMN erc20 VARCHAR(255);
    END IF;
    IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'passport_data') THEN
        ALTER TABLE users ADD COLUMN passport_data VARCHAR(255);
    END IF;
END $$;

-- Sanity check на blob size. Floor 100 (worst-case 12-word всё-3char mnemonic):
--   plaintext 47 bytes + IV(12) + tag(16) = 75 raw → 100 base64
--   typical 12-word: 113 raw → 152 base64; 24-word: 240 raw → 320 base64
-- (Раньше floor 140 отвергал ~4% валидных 12-word mnemonics — fixed.)
DO $$
BEGIN
    IF EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_encrypted_mnemonic_size') THEN
        ALTER TABLE users DROP CONSTRAINT users_encrypted_mnemonic_size;
    END IF;
    ALTER TABLE users
        ADD CONSTRAINT users_encrypted_mnemonic_size
        CHECK (encrypted_mnemonic IS NULL OR (char_length(encrypted_mnemonic) BETWEEN 100 AND 512));
END $$;

-- Case-insensitive email uniqueness (Alice@x.com ≠ alice@x.com → ACCOUNT HIJACKING fix)
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'users_email_lower_unique') THEN
        CREATE UNIQUE INDEX users_email_lower_unique ON users (lower(email));
    END IF;
END $$;

-- Partial index для active-user queries
CREATE INDEX IF NOT EXISTS idx_users_active ON users(id) WHERE is_deleted = FALSE;

-- erc20 format check (NULL or 0x + 40 hex)
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_erc20_format') THEN
        ALTER TABLE users
            ADD CONSTRAINT users_erc20_format
            CHECK (erc20 IS NULL OR erc20 ~ '^0x[0-9a-fA-F]{40}$');
    END IF;
END $$;

-- KYC consistency: verified=true requires verified_at NOT NULL
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'users_kyc_consistency') THEN
        ALTER TABLE users
            ADD CONSTRAINT users_kyc_consistency
            CHECK ((kyc_verified = FALSE) OR (kyc_verified = TRUE AND kyc_verified_at IS NOT NULL));
    END IF;
END $$;

-- ── WALLETS ─────────────────────────────────────────────────────────
-- ON DELETE RESTRICT: hard-delete user → запрос отвергнут пока есть wallets.
-- Это защита от unrecoverable fund loss при GDPR-wipe или admin удалении.
-- Use is_deleted=true для soft-delete.
CREATE TABLE IF NOT EXISTS wallets (
    id                  VARCHAR(26)               NOT NULL PRIMARY KEY,
    user_id             VARCHAR(26)               NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
    chain               VARCHAR(16)               NOT NULL,
    address             VARCHAR(128)              NOT NULL,
    derivation_path     VARCHAR(64)               NOT NULL,
    created_at          TIMESTAMP WITH TIME ZONE  NOT NULL DEFAULT CURRENT_TIMESTAMP,
    UNIQUE (user_id, chain),
    CHECK (chain IN ('ETH', 'BSC', 'BTC', 'TRX', 'SOL'))
);

CREATE INDEX IF NOT EXISTS idx_wallets_user_id ON wallets(user_id);
CREATE INDEX IF NOT EXISTS idx_wallets_address ON wallets(address);

-- Idempotent FK migration: если raised на старой DB с CASCADE — поменять
DO $$
BEGIN
    IF EXISTS (
        SELECT 1 FROM information_schema.referential_constraints
        WHERE constraint_name LIKE 'wallets_user_id_fkey%' AND delete_rule = 'CASCADE'
    ) THEN
        ALTER TABLE wallets DROP CONSTRAINT IF EXISTS wallets_user_id_fkey;
        ALTER TABLE wallets ADD CONSTRAINT wallets_user_id_fkey
            FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE RESTRICT;
    END IF;
END $$;

-- ── AUDIT_LOG (durable sink для критических custodial операций) ─────
-- Pre-mutation INSERT 'pending', post-mutation UPDATE 'completed' с txid.
-- Если INSERT fails — операция НЕ происходит (fail-secure).
CREATE TABLE IF NOT EXISTS audit_log (
    id                  VARCHAR(26)               NOT NULL PRIMARY KEY,
    user_id             VARCHAR(26)               NOT NULL,
    event               VARCHAR(64)               NOT NULL,
    status              VARCHAR(16)               NOT NULL DEFAULT 'pending'
        CHECK (status IN ('pending', 'success', 'failure')),
    error_code          VARCHAR(64),
    ip                  VARCHAR(64),
    trace_id            VARCHAR(64),
    meta                JSONB,
    created_at          TIMESTAMP WITH TIME ZONE  NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at          TIMESTAMP WITH TIME ZONE  NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_audit_log_user_id ON audit_log(user_id);
CREATE INDEX IF NOT EXISTS idx_audit_log_event_created ON audit_log(event, created_at DESC);

-- ── IDEMPOTENCY_KEYS (защита от double-spend на retry) ──────────────
-- Client шлёт Idempotency-Key header. Pre-mutation INSERT row, post-mutation UPDATE с response.
-- На retry — возвращаем cached response без второго broadcast.
CREATE TABLE IF NOT EXISTS idempotency_keys (
    user_id             VARCHAR(26)               NOT NULL,
    key                 VARCHAR(128)              NOT NULL,
    request_hash        VARCHAR(64)               NOT NULL,
    response_status     SMALLINT,
    response_body       TEXT,
    created_at          TIMESTAMP WITH TIME ZONE  NOT NULL DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (user_id, key)
);

CREATE INDEX IF NOT EXISTS idx_idempotency_keys_created ON idempotency_keys(created_at);
-- Retention cleanup (run via cron): DELETE FROM idempotency_keys WHERE created_at < NOW() - INTERVAL '24 hours';
