-- =============================================================
-- ARCHIVO: 13-storage.sql
-- DESCRIPCION: Sistema de almacenamiento de archivos, carpetas, uploads
-- VERSION: 1.0.0
-- PROYECTO: ERP-Core V2
-- FECHA: 2026-01-10
-- EPIC: SAAS-STORAGE (EPIC-SAAS-006)
-- HISTORIAS: US-070, US-071, US-072
-- =============================================================

-- =====================
-- SCHEMA: storage
-- =====================
CREATE SCHEMA IF NOT EXISTS storage;

-- =====================
-- TABLA: storage.buckets
-- Contenedores de almacenamiento
-- =====================
CREATE TABLE IF NOT EXISTS storage.buckets (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),

    -- Identificación
    name VARCHAR(100) NOT NULL UNIQUE,
    description TEXT,

    -- Tipo
    bucket_type VARCHAR(30) NOT NULL DEFAULT 'private',
    -- public: acceso público sin autenticación
    -- private: requiere autenticación
    -- protected: requiere token temporal

    -- Configuración
    max_file_size_mb INTEGER DEFAULT 50,
    allowed_mime_types TEXT[] DEFAULT '{}', -- Vacío = todos permitidos
    allowed_extensions TEXT[] DEFAULT '{}',

    -- Políticas
    auto_delete_days INTEGER, -- NULL = no auto-eliminar
    versioning_enabled BOOLEAN DEFAULT FALSE,
    max_versions INTEGER DEFAULT 5,

    -- Storage backend
    storage_provider VARCHAR(30) DEFAULT 'local', -- local, s3, gcs, azure
    storage_config JSONB DEFAULT '{}',

    -- Límites por tenant
    quota_per_tenant_gb INTEGER,

    -- Estado
    is_active BOOLEAN DEFAULT TRUE,
    is_system BOOLEAN DEFAULT FALSE,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

-- =====================
-- TABLA: storage.folders
-- Estructura de carpetas virtuales
-- =====================
CREATE TABLE IF NOT EXISTS storage.folders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
    bucket_id UUID NOT NULL REFERENCES storage.buckets(id) ON DELETE CASCADE,

    -- Jerarquía
    parent_id UUID REFERENCES storage.folders(id) ON DELETE CASCADE,
    path TEXT NOT NULL, -- /documents/invoices/2026/
    name VARCHAR(255) NOT NULL,
    depth INTEGER DEFAULT 0,

    -- Metadata
    description TEXT,
    color VARCHAR(7), -- Color hex para UI
    icon VARCHAR(50),

    -- Permisos
    is_private BOOLEAN DEFAULT FALSE,
    owner_id UUID REFERENCES auth.users(id),

    -- Estadísticas (actualizadas async)
    file_count INTEGER DEFAULT 0,
    total_size_bytes BIGINT DEFAULT 0,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES auth.users(id),

    UNIQUE(tenant_id, bucket_id, path)
);

-- =====================
-- TABLA: storage.files
-- Archivos almacenados
-- =====================
CREATE TABLE IF NOT EXISTS storage.files (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
    bucket_id UUID NOT NULL REFERENCES storage.buckets(id) ON DELETE CASCADE,
    folder_id UUID REFERENCES storage.folders(id) ON DELETE SET NULL,

    -- Identificación
    name VARCHAR(255) NOT NULL,
    original_name VARCHAR(255) NOT NULL,
    path TEXT NOT NULL, -- Ruta completa en storage

    -- Tipo de archivo
    mime_type VARCHAR(100) NOT NULL,
    extension VARCHAR(20),
    category VARCHAR(30), -- image, document, video, audio, archive, other

    -- Tamaño
    size_bytes BIGINT NOT NULL,

    -- Hashes para integridad y deduplicación
    checksum_md5 VARCHAR(32),
    checksum_sha256 VARCHAR(64),

    -- Almacenamiento
    storage_key TEXT NOT NULL, -- Key en el backend de storage
    storage_url TEXT, -- URL directa (si aplica)
    cdn_url TEXT, -- URL de CDN (si aplica)

    -- Imagen (si aplica)
    width INTEGER,
    height INTEGER,
    thumbnail_url TEXT,
    thumbnails JSONB DEFAULT '{}', -- {small: url, medium: url, large: url}

    -- Metadata
    metadata JSONB DEFAULT '{}',
    tags TEXT[] DEFAULT '{}',
    alt_text TEXT,

    -- Versionamiento
    version INTEGER DEFAULT 1,
    parent_version_id UUID REFERENCES storage.files(id),
    is_latest BOOLEAN DEFAULT TRUE,

    -- Asociación con entidades
    entity_type VARCHAR(100), -- product, user, invoice, etc.
    entity_id UUID,

    -- Acceso
    is_public BOOLEAN DEFAULT FALSE,
    access_count INTEGER DEFAULT 0,
    last_accessed_at TIMESTAMPTZ,

    -- Estado
    status VARCHAR(20) DEFAULT 'active', -- active, processing, archived, deleted
    archived_at TIMESTAMPTZ,
    deleted_at TIMESTAMPTZ,

    -- Procesamiento
    processing_status VARCHAR(20), -- pending, processing, completed, failed
    processing_error TEXT,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    uploaded_by UUID REFERENCES auth.users(id),

    UNIQUE(tenant_id, bucket_id, path, version)
);

-- =====================
-- TABLA: storage.file_access_tokens
-- Tokens de acceso temporal a archivos
-- =====================
CREATE TABLE IF NOT EXISTS storage.file_access_tokens (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    file_id UUID NOT NULL REFERENCES storage.files(id) ON DELETE CASCADE,
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

    -- Token
    token VARCHAR(255) NOT NULL UNIQUE,

    -- Permisos
    permissions TEXT[] DEFAULT '{read}', -- read, download, write

    -- Restricciones
    allowed_ips INET[],
    max_downloads INTEGER,
    download_count INTEGER DEFAULT 0,

    -- Validez
    expires_at TIMESTAMPTZ NOT NULL,
    revoked_at TIMESTAMPTZ,

    -- Metadata
    created_for VARCHAR(255), -- Email o nombre para quien se creó
    purpose TEXT,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES auth.users(id)
);

-- =====================
-- TABLA: storage.uploads
-- Uploads en progreso (multipart, resumable)
-- =====================
CREATE TABLE IF NOT EXISTS storage.uploads (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
    bucket_id UUID NOT NULL REFERENCES storage.buckets(id) ON DELETE CASCADE,
    folder_id UUID REFERENCES storage.folders(id),

    -- Archivo destino
    file_name VARCHAR(255) NOT NULL,
    mime_type VARCHAR(100),
    total_size_bytes BIGINT,

    -- Estado del upload
    status VARCHAR(20) NOT NULL DEFAULT 'pending',
    -- pending, uploading, processing, completed, failed, cancelled

    -- Progreso
    uploaded_bytes BIGINT DEFAULT 0,
    upload_progress DECIMAL(5,2) DEFAULT 0,

    -- Chunks (para multipart)
    total_chunks INTEGER,
    completed_chunks INTEGER DEFAULT 0,
    chunk_size_bytes INTEGER,
    chunks_status JSONB DEFAULT '{}', -- {0: 'completed', 1: 'pending', ...}

    -- Metadata
    metadata JSONB DEFAULT '{}',

    -- Resultado
    file_id UUID REFERENCES storage.files(id),
    error_message TEXT,

    -- Tiempos
    started_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    last_chunk_at TIMESTAMPTZ,
    completed_at TIMESTAMPTZ,
    expires_at TIMESTAMPTZ,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES auth.users(id)
);

-- =====================
-- TABLA: storage.file_shares
-- Archivos compartidos
-- =====================
CREATE TABLE IF NOT EXISTS storage.file_shares (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    file_id UUID NOT NULL REFERENCES storage.files(id) ON DELETE CASCADE,
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,

    -- Compartido con
    shared_with_user_id UUID REFERENCES auth.users(id),
    shared_with_email VARCHAR(255),
    shared_with_role VARCHAR(50),

    -- Permisos
    can_view BOOLEAN DEFAULT TRUE,
    can_download BOOLEAN DEFAULT TRUE,
    can_edit BOOLEAN DEFAULT FALSE,
    can_delete BOOLEAN DEFAULT FALSE,
    can_share BOOLEAN DEFAULT FALSE,

    -- Link público
    public_link VARCHAR(255) UNIQUE,
    public_link_password VARCHAR(255),

    -- Validez
    expires_at TIMESTAMPTZ,
    revoked_at TIMESTAMPTZ,

    -- Estadísticas
    view_count INTEGER DEFAULT 0,
    download_count INTEGER DEFAULT 0,
    last_accessed_at TIMESTAMPTZ,

    -- Notificaciones
    notify_on_access BOOLEAN DEFAULT FALSE,

    created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
    created_by UUID REFERENCES auth.users(id),
    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);

-- =====================
-- TABLA: storage.tenant_usage
-- Uso de storage por tenant
-- =====================
CREATE TABLE IF NOT EXISTS storage.tenant_usage (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
    bucket_id UUID NOT NULL REFERENCES storage.buckets(id) ON DELETE CASCADE,

    -- Uso actual
    file_count INTEGER DEFAULT 0,
    total_size_bytes BIGINT DEFAULT 0,

    -- Límites
    quota_bytes BIGINT,
    quota_file_count INTEGER,

    -- Uso por categoría
    usage_by_category JSONB DEFAULT '{}',
    -- {image: 1024000, document: 2048000, ...}

    -- Histórico mensual
    monthly_upload_bytes BIGINT DEFAULT 0,
    monthly_download_bytes BIGINT DEFAULT 0,
    month_year VARCHAR(7), -- 2026-01

    updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,

    UNIQUE(tenant_id, bucket_id, month_year)
);

-- =====================
-- INDICES
-- =====================

-- Indices para buckets
CREATE INDEX IF NOT EXISTS idx_buckets_name ON storage.buckets(name);
CREATE INDEX IF NOT EXISTS idx_buckets_active ON storage.buckets(is_active) WHERE is_active = TRUE;

-- Indices para folders
CREATE INDEX IF NOT EXISTS idx_folders_tenant ON storage.folders(tenant_id);
CREATE INDEX IF NOT EXISTS idx_folders_bucket ON storage.folders(bucket_id);
CREATE INDEX IF NOT EXISTS idx_folders_parent ON storage.folders(parent_id);
CREATE INDEX IF NOT EXISTS idx_folders_path ON storage.folders(path);

-- Indices para files
CREATE INDEX IF NOT EXISTS idx_files_tenant ON storage.files(tenant_id);
CREATE INDEX IF NOT EXISTS idx_files_bucket ON storage.files(bucket_id);
CREATE INDEX IF NOT EXISTS idx_files_folder ON storage.files(folder_id);
CREATE INDEX IF NOT EXISTS idx_files_entity ON storage.files(entity_type, entity_id);
CREATE INDEX IF NOT EXISTS idx_files_mime ON storage.files(mime_type);
CREATE INDEX IF NOT EXISTS idx_files_category ON storage.files(category);
CREATE INDEX IF NOT EXISTS idx_files_status ON storage.files(status);
CREATE INDEX IF NOT EXISTS idx_files_checksum ON storage.files(checksum_sha256);
CREATE INDEX IF NOT EXISTS idx_files_created ON storage.files(created_at DESC);
CREATE INDEX IF NOT EXISTS idx_files_tags ON storage.files USING GIN(tags);
CREATE INDEX IF NOT EXISTS idx_files_latest ON storage.files(parent_version_id) WHERE is_latest = TRUE;

-- Indices para file_access_tokens
CREATE INDEX IF NOT EXISTS idx_access_tokens_file ON storage.file_access_tokens(file_id);
CREATE INDEX IF NOT EXISTS idx_access_tokens_token ON storage.file_access_tokens(token);
CREATE INDEX IF NOT EXISTS idx_access_tokens_valid ON storage.file_access_tokens(expires_at)
    WHERE revoked_at IS NULL;

-- Indices para uploads
CREATE INDEX IF NOT EXISTS idx_uploads_tenant ON storage.uploads(tenant_id);
CREATE INDEX IF NOT EXISTS idx_uploads_status ON storage.uploads(status);
CREATE INDEX IF NOT EXISTS idx_uploads_expires ON storage.uploads(expires_at) WHERE status = 'uploading';

-- Indices para file_shares
CREATE INDEX IF NOT EXISTS idx_shares_file ON storage.file_shares(file_id);
CREATE INDEX IF NOT EXISTS idx_shares_user ON storage.file_shares(shared_with_user_id);
CREATE INDEX IF NOT EXISTS idx_shares_link ON storage.file_shares(public_link);

-- Indices para tenant_usage
CREATE INDEX IF NOT EXISTS idx_usage_tenant ON storage.tenant_usage(tenant_id);
CREATE INDEX IF NOT EXISTS idx_usage_bucket ON storage.tenant_usage(bucket_id);

-- =====================
-- RLS POLICIES
-- =====================

-- Buckets son globales (lectura pública)
ALTER TABLE storage.buckets ENABLE ROW LEVEL SECURITY;
CREATE POLICY public_read_buckets ON storage.buckets
    FOR SELECT USING (is_active = TRUE);

-- Folders por tenant
ALTER TABLE storage.folders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_folders ON storage.folders
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- Files por tenant
ALTER TABLE storage.files ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_files ON storage.files
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- Access tokens por tenant
ALTER TABLE storage.file_access_tokens ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_tokens ON storage.file_access_tokens
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- Uploads por tenant
ALTER TABLE storage.uploads ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_uploads ON storage.uploads
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- File shares por tenant
ALTER TABLE storage.file_shares ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_shares ON storage.file_shares
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- Tenant usage por tenant
ALTER TABLE storage.tenant_usage ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation_usage ON storage.tenant_usage
    USING (tenant_id = current_setting('app.current_tenant_id', true)::uuid);

-- =====================
-- FUNCIONES
-- =====================

-- Función para generar storage key único
CREATE OR REPLACE FUNCTION storage.generate_storage_key(
    p_tenant_id UUID,
    p_bucket_name VARCHAR(100),
    p_file_name VARCHAR(255)
)
RETURNS TEXT AS $$
BEGIN
    RETURN p_bucket_name || '/' ||
           p_tenant_id::TEXT || '/' ||
           TO_CHAR(CURRENT_DATE, 'YYYY/MM/DD') || '/' ||
           gen_random_uuid()::TEXT || '/' ||
           p_file_name;
END;
$$ LANGUAGE plpgsql;

-- Función para determinar categoría por mime type
CREATE OR REPLACE FUNCTION storage.get_file_category(p_mime_type VARCHAR(100))
RETURNS VARCHAR(30) AS $$
BEGIN
    RETURN CASE
        WHEN p_mime_type LIKE 'image/%' THEN 'image'
        WHEN p_mime_type LIKE 'video/%' THEN 'video'
        WHEN p_mime_type LIKE 'audio/%' THEN 'audio'
        WHEN p_mime_type IN ('application/pdf', 'application/msword',
            'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
            'application/vnd.ms-excel',
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'text/plain', 'text/csv') THEN 'document'
        WHEN p_mime_type IN ('application/zip', 'application/x-rar-compressed',
            'application/x-7z-compressed', 'application/gzip') THEN 'archive'
        ELSE 'other'
    END;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- Función para crear archivo
CREATE OR REPLACE FUNCTION storage.create_file(
    p_tenant_id UUID,
    p_bucket_id UUID,
    p_folder_id UUID,
    p_name VARCHAR(255),
    p_original_name VARCHAR(255),
    p_mime_type VARCHAR(100),
    p_size_bytes BIGINT,
    p_storage_key TEXT,
    p_uploaded_by UUID DEFAULT NULL,
    p_metadata JSONB DEFAULT '{}'
)
RETURNS UUID AS $$
DECLARE
    v_file_id UUID;
    v_bucket RECORD;
    v_path TEXT;
    v_category VARCHAR(30);
BEGIN
    -- Verificar bucket
    SELECT * INTO v_bucket FROM storage.buckets WHERE id = p_bucket_id;
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Bucket not found';
    END IF;

    -- Verificar tamaño
    IF v_bucket.max_file_size_mb IS NOT NULL AND
       p_size_bytes > v_bucket.max_file_size_mb * 1024 * 1024 THEN
        RAISE EXCEPTION 'File size exceeds bucket limit';
    END IF;

    -- Obtener path de la carpeta
    IF p_folder_id IS NOT NULL THEN
        SELECT path INTO v_path FROM storage.folders WHERE id = p_folder_id;
        v_path := v_path || p_name;
    ELSE
        v_path := '/' || p_name;
    END IF;

    -- Determinar categoría
    v_category := storage.get_file_category(p_mime_type);

    -- Crear archivo
    INSERT INTO storage.files (
        tenant_id, bucket_id, folder_id,
        name, original_name, path,
        mime_type, extension, category,
        size_bytes, storage_key,
        metadata, uploaded_by
    ) VALUES (
        p_tenant_id, p_bucket_id, p_folder_id,
        p_name, p_original_name, v_path,
        p_mime_type, LOWER(SPLIT_PART(p_name, '.', -1)), v_category,
        p_size_bytes, p_storage_key,
        p_metadata, p_uploaded_by
    ) RETURNING id INTO v_file_id;

    -- Actualizar estadísticas de carpeta
    IF p_folder_id IS NOT NULL THEN
        UPDATE storage.folders
        SET file_count = file_count + 1,
            total_size_bytes = total_size_bytes + p_size_bytes,
            updated_at = CURRENT_TIMESTAMP
        WHERE id = p_folder_id;
    END IF;

    -- Actualizar uso del tenant
    INSERT INTO storage.tenant_usage (tenant_id, bucket_id, file_count, total_size_bytes, month_year)
    VALUES (p_tenant_id, p_bucket_id, 1, p_size_bytes, TO_CHAR(CURRENT_DATE, 'YYYY-MM'))
    ON CONFLICT (tenant_id, bucket_id, month_year)
    DO UPDATE SET
        file_count = storage.tenant_usage.file_count + 1,
        total_size_bytes = storage.tenant_usage.total_size_bytes + p_size_bytes,
        updated_at = CURRENT_TIMESTAMP;

    RETURN v_file_id;
END;
$$ LANGUAGE plpgsql;

-- Función para crear token de acceso
CREATE OR REPLACE FUNCTION storage.create_access_token(
    p_file_id UUID,
    p_expires_in_hours INTEGER DEFAULT 24,
    p_permissions TEXT[] DEFAULT '{read}',
    p_max_downloads INTEGER DEFAULT NULL,
    p_created_by UUID DEFAULT NULL
)
RETURNS TEXT AS $$
DECLARE
    v_token TEXT;
    v_tenant_id UUID;
BEGIN
    -- Obtener tenant del archivo
    SELECT tenant_id INTO v_tenant_id FROM storage.files WHERE id = p_file_id;
    IF NOT FOUND THEN
        RAISE EXCEPTION 'File not found';
    END IF;

    -- Generar token
    v_token := 'sat_' || encode(gen_random_bytes(32), 'hex');

    -- Crear registro
    INSERT INTO storage.file_access_tokens (
        file_id, tenant_id, token, permissions,
        max_downloads, expires_at, created_by
    ) VALUES (
        p_file_id, v_tenant_id, v_token, p_permissions,
        p_max_downloads,
        CURRENT_TIMESTAMP + (p_expires_in_hours || ' hours')::INTERVAL,
        p_created_by
    );

    RETURN v_token;
END;
$$ LANGUAGE plpgsql;

-- Función para validar token de acceso
CREATE OR REPLACE FUNCTION storage.validate_access_token(
    p_token VARCHAR(255),
    p_permission VARCHAR(20) DEFAULT 'read'
)
RETURNS TABLE (
    is_valid BOOLEAN,
    file_id UUID,
    tenant_id UUID,
    error_message TEXT
) AS $$
DECLARE
    v_token RECORD;
BEGIN
    SELECT * INTO v_token
    FROM storage.file_access_tokens
    WHERE token = p_token;

    IF NOT FOUND THEN
        RETURN QUERY SELECT FALSE, NULL::UUID, NULL::UUID, 'Token not found'::TEXT;
        RETURN;
    END IF;

    IF v_token.revoked_at IS NOT NULL THEN
        RETURN QUERY SELECT FALSE, NULL::UUID, NULL::UUID, 'Token revoked'::TEXT;
        RETURN;
    END IF;

    IF v_token.expires_at < CURRENT_TIMESTAMP THEN
        RETURN QUERY SELECT FALSE, NULL::UUID, NULL::UUID, 'Token expired'::TEXT;
        RETURN;
    END IF;

    IF NOT (p_permission = ANY(v_token.permissions)) THEN
        RETURN QUERY SELECT FALSE, NULL::UUID, NULL::UUID, 'Permission denied'::TEXT;
        RETURN;
    END IF;

    IF v_token.max_downloads IS NOT NULL AND
       p_permission = 'download' AND
       v_token.download_count >= v_token.max_downloads THEN
        RETURN QUERY SELECT FALSE, NULL::UUID, NULL::UUID, 'Download limit reached'::TEXT;
        RETURN;
    END IF;

    -- Incrementar contador si es download
    IF p_permission = 'download' THEN
        UPDATE storage.file_access_tokens
        SET download_count = download_count + 1
        WHERE id = v_token.id;
    END IF;

    RETURN QUERY SELECT TRUE, v_token.file_id, v_token.tenant_id, NULL::TEXT;
END;
$$ LANGUAGE plpgsql;

-- Función para obtener uso del tenant
CREATE OR REPLACE FUNCTION storage.get_tenant_usage(p_tenant_id UUID)
RETURNS TABLE (
    total_files BIGINT,
    total_size_bytes BIGINT,
    total_size_mb DECIMAL,
    usage_by_bucket JSONB,
    usage_by_category JSONB
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        COALESCE(SUM(tu.file_count), 0)::BIGINT as total_files,
        COALESCE(SUM(tu.total_size_bytes), 0)::BIGINT as total_size_bytes,
        ROUND(COALESCE(SUM(tu.total_size_bytes), 0)::DECIMAL / 1024 / 1024, 2) as total_size_mb,
        jsonb_object_agg(b.name, tu.total_size_bytes) as usage_by_bucket,
        COALESCE(
            (SELECT jsonb_object_agg(category, cat_size)
             FROM (
                 SELECT f.category, SUM(f.size_bytes) as cat_size
                 FROM storage.files f
                 WHERE f.tenant_id = p_tenant_id AND f.status = 'active'
                 GROUP BY f.category
             ) cats),
            '{}'::JSONB
        ) as usage_by_category
    FROM storage.tenant_usage tu
    JOIN storage.buckets b ON b.id = tu.bucket_id
    WHERE tu.tenant_id = p_tenant_id
    AND tu.month_year = TO_CHAR(CURRENT_DATE, 'YYYY-MM');
END;
$$ LANGUAGE plpgsql STABLE;

-- Función para limpiar archivos expirados
CREATE OR REPLACE FUNCTION storage.cleanup_expired_files()
RETURNS INTEGER AS $$
DECLARE
    deleted_count INTEGER := 0;
    v_bucket RECORD;
BEGIN
    -- Procesar cada bucket con auto_delete_days
    FOR v_bucket IN SELECT * FROM storage.buckets WHERE auto_delete_days IS NOT NULL LOOP
        UPDATE storage.files
        SET status = 'deleted', deleted_at = CURRENT_TIMESTAMP
        WHERE bucket_id = v_bucket.id
        AND status = 'active'
        AND created_at < CURRENT_TIMESTAMP - (v_bucket.auto_delete_days || ' days')::INTERVAL;

        deleted_count := deleted_count + ROW_COUNT;
    END LOOP;

    -- Limpiar tokens expirados
    DELETE FROM storage.file_access_tokens
    WHERE expires_at < CURRENT_TIMESTAMP - INTERVAL '7 days';

    -- Limpiar uploads abandonados
    DELETE FROM storage.uploads
    WHERE status IN ('pending', 'uploading')
    AND expires_at < CURRENT_TIMESTAMP;

    RETURN deleted_count;
END;
$$ LANGUAGE plpgsql;

-- =====================
-- TRIGGERS
-- =====================

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

CREATE TRIGGER trg_buckets_updated_at
    BEFORE UPDATE ON storage.buckets
    FOR EACH ROW EXECUTE FUNCTION storage.update_timestamp();

CREATE TRIGGER trg_folders_updated_at
    BEFORE UPDATE ON storage.folders
    FOR EACH ROW EXECUTE FUNCTION storage.update_timestamp();

CREATE TRIGGER trg_files_updated_at
    BEFORE UPDATE ON storage.files
    FOR EACH ROW EXECUTE FUNCTION storage.update_timestamp();

CREATE TRIGGER trg_shares_updated_at
    BEFORE UPDATE ON storage.file_shares
    FOR EACH ROW EXECUTE FUNCTION storage.update_timestamp();

-- =====================
-- SEED DATA: Buckets del Sistema
-- =====================
INSERT INTO storage.buckets (name, description, bucket_type, max_file_size_mb, allowed_mime_types, is_system) VALUES
('avatars', 'Avatares de usuarios', 'public', 5, '{image/jpeg,image/png,image/gif,image/webp}', TRUE),
('logos', 'Logos de empresas', 'public', 10, '{image/jpeg,image/png,image/svg+xml,image/webp}', TRUE),
('documents', 'Documentos generales', 'private', 50, '{}', TRUE),
('invoices', 'Facturas y comprobantes', 'private', 20, '{application/pdf,image/jpeg,image/png}', TRUE),
('products', 'Imágenes de productos', 'public', 10, '{image/jpeg,image/png,image/webp}', TRUE),
('attachments', 'Archivos adjuntos', 'private', 25, '{}', TRUE),
('exports', 'Exportaciones de datos', 'protected', 500, '{application/zip,text/csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet}', TRUE),
('backups', 'Respaldos', 'private', 1000, '{application/zip,application/gzip}', TRUE)
ON CONFLICT (name) DO NOTHING;

-- =====================
-- COMENTARIOS
-- =====================
COMMENT ON TABLE storage.buckets IS 'Contenedores de almacenamiento configurables';
COMMENT ON TABLE storage.folders IS 'Estructura de carpetas virtuales por tenant';
COMMENT ON TABLE storage.files IS 'Archivos almacenados con metadata y versionamiento';
COMMENT ON TABLE storage.file_access_tokens IS 'Tokens de acceso temporal a archivos';
COMMENT ON TABLE storage.uploads IS 'Uploads en progreso (multipart/resumable)';
COMMENT ON TABLE storage.file_shares IS 'Configuración de archivos compartidos';
COMMENT ON TABLE storage.tenant_usage IS 'Uso de storage por tenant y bucket';

COMMENT ON FUNCTION storage.create_file IS 'Crea un registro de archivo con validaciones';
COMMENT ON FUNCTION storage.create_access_token IS 'Genera un token de acceso temporal';
COMMENT ON FUNCTION storage.validate_access_token IS 'Valida un token de acceso';
COMMENT ON FUNCTION storage.get_tenant_usage IS 'Obtiene estadísticas de uso de storage';
