"""Add vulnerability_packages table + backfill from parent columns

Revision ID: 023
Revises: 022
Create Date: 2026-05-25 12:00:00.000000

Wazuh + Nessus can report the same CVE affecting MULTIPLE packages on
the same asset (e.g. CVE-2023-48795 hits both PuTTY 0.73 and WinSCP
6.1.2 on a single host). Until now we merged them into a single
comma-joined string on `vulnerabilities.package_name` and stored ONE
`fixed_version` — losing per-package patch tracking.

This migration adds a child table `vulnerability_packages` with one
row per (vulnerability, package). Backfill: every existing
Vulnerability row gets a single child carrying the joined string
verbatim (no split — joined names may contain commas in version
strings). Forward syncs will write one row per detected package.

Idempotent — uses IF NOT EXISTS / NOT EXISTS guards.
"""
from alembic import op
import sqlalchemy as sa


revision = "023"
down_revision = "022"
branch_labels = None
depends_on = None


def upgrade() -> None:
    op.execute("""
        CREATE TABLE IF NOT EXISTS vulnerability_packages (
            id SERIAL PRIMARY KEY,
            vulnerability_id INTEGER NOT NULL REFERENCES vulnerabilities(id) ON DELETE CASCADE,
            package_name VARCHAR(255) NOT NULL,
            package_version VARCHAR(100),
            fixed_version VARCHAR(100),
            source VARCHAR(20),
            first_detected_at TIMESTAMP NOT NULL DEFAULT now(),
            last_seen_at TIMESTAMP NOT NULL DEFAULT now(),
            created_at TIMESTAMP NOT NULL DEFAULT now(),
            updated_at TIMESTAMP NOT NULL DEFAULT now(),
            CONSTRAINT uq_vulnpkg_vuln_package UNIQUE (vulnerability_id, package_name)
        );
    """)
    op.execute("""
        CREATE INDEX IF NOT EXISTS ix_vulnerability_packages_vulnerability_id
        ON vulnerability_packages (vulnerability_id);
    """)
    op.execute("""
        CREATE INDEX IF NOT EXISTS ix_vulnerability_packages_package_name
        ON vulnerability_packages (package_name);
    """)
    op.execute("""
        CREATE INDEX IF NOT EXISTS ix_vulnpkg_name_fixed
        ON vulnerability_packages (package_name, fixed_version);
    """)

    # Backfill: one child per existing vuln carrying the joined string
    # verbatim. Splitting on comma is unsafe (versions may contain
    # commas, packages may contain commas). Forward syncs will write
    # proper per-package rows and stale joined ones get expired by the
    # sync's mass-patch guard.
    op.execute("""
        INSERT INTO vulnerability_packages
            (vulnerability_id, package_name, package_version, fixed_version, source,
             first_detected_at, last_seen_at)
        SELECT
            v.id,
            COALESCE(v.package_name, '(unknown)'),
            v.package_version,
            v.fixed_version,
            v.first_detected_by,
            v.detected_at,
            COALESCE(v.updated_at, v.detected_at)
        FROM vulnerabilities v
        WHERE NOT EXISTS (
            SELECT 1 FROM vulnerability_packages p
            WHERE p.vulnerability_id = v.id
        );
    """)


def downgrade() -> None:
    op.execute("DROP TABLE IF EXISTS vulnerability_packages CASCADE;")
