"""Add AI analysis tables

Revision ID: 436c6f3e5e82
Revises: 44b803f122aa
Create Date: 2026-02-26 09:33:47.700449

"""

from typing import Sequence, Union

import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

from alembic import op

# revision identifiers, used by Alembic.
revision: str = "436c6f3e5e82"
down_revision: Union[str, None] = "44b803f122aa"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table(
        "recommendation_execution",
        sa.Column(
            "id", sa.UUID(), server_default=sa.text("gen_random_uuid()"), nullable=False
        ),
        sa.Column("tenant", sa.UUID(), nullable=False),
        sa.Column(
            "execution_start",
            sa.DateTime(timezone=True),
            server_default=sa.text("NOW()"),
            nullable=False,
        ),
        sa.Column("execution_end", sa.DateTime(timezone=True), nullable=True),
        sa.Column("duration_seconds", sa.Numeric(precision=10, scale=3), nullable=True),
        sa.Column("analysis_period_start", sa.DateTime(timezone=True), nullable=False),
        sa.Column("analysis_period_end", sa.DateTime(timezone=True), nullable=False),
        sa.Column("status", sa.Text(), server_default="success", nullable=False),
        sa.Column(
            "recommendations_generated",
            sa.Integer(),
            server_default="0",
            nullable=False,
        ),
        sa.Column("devices_analyzed", sa.Integer(), server_default="0", nullable=False),
        sa.Column("error_message", sa.Text(), nullable=True),
        sa.Column("triggered_by", sa.Text(), nullable=True),
        sa.Column("airflow_run_id", sa.Text(), nullable=True),
        sa.Column("airflow_task_id", sa.Text(), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("NOW()"),
            nullable=False,
        ),
        sa.CheckConstraint(
            "status IN ('success', 'partial', 'failed', 'skipped')",
            name="chk_rec_exec_status",
        ),
        sa.PrimaryKeyConstraint("id"),
    )
    op.create_index(
        "idx_rec_exec_status", "recommendation_execution", ["status"], unique=False
    )
    op.create_index(
        "idx_rec_exec_tenant_start",
        "recommendation_execution",
        ["tenant", "execution_start"],
        unique=False,
    )
    op.create_index(
        op.f("ix_recommendation_execution_tenant"),
        "recommendation_execution",
        ["tenant"],
        unique=False,
    )
    op.create_table(
        "ai_analysis_config",
        sa.Column(
            "id", sa.UUID(), server_default=sa.text("gen_random_uuid()"), nullable=False
        ),
        sa.Column("tenant", sa.UUID(), nullable=False),
        sa.Column("device_id", sa.Integer(), nullable=True),
        sa.Column("algorithm_type", sa.Text(), nullable=False),
        sa.Column(
            "config_params",
            postgresql.JSONB(astext_type=sa.Text()),
            server_default="{}",
            nullable=False,
        ),
        sa.Column("enabled", sa.Boolean(), server_default="true", nullable=False),
        sa.Column("priority", sa.Integer(), server_default="0", nullable=False),
        sa.Column("schedule_cron", sa.Text(), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("NOW()"),
            nullable=False,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("NOW()"),
            nullable=False,
        ),
        sa.Column("created_by", sa.Text(), nullable=True),
        sa.CheckConstraint(
            "algorithm_type IN ('anomaly_zscore', 'anomaly_iqr', 'leak_detection', 'isolation_forest', 'correlation_analysis', 'lstm_autoencoder', 'prophet_forecast')",
            name="chk_ai_config_algorithm_type",
        ),
        sa.ForeignKeyConstraint(["device_id"], ["dim_device.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
        sa.UniqueConstraint(
            "tenant",
            "device_id",
            "algorithm_type",
            name="uq_ai_config_tenant_device_algo",
        ),
    )
    op.create_index(
        "idx_ai_config_algorithm",
        "ai_analysis_config",
        ["algorithm_type"],
        unique=False,
    )
    op.create_index(
        "idx_ai_config_enabled",
        "ai_analysis_config",
        ["enabled"],
        unique=False,
        postgresql_where=sa.text("enabled = TRUE"),
    )
    op.create_index(
        "idx_ai_config_tenant_enabled",
        "ai_analysis_config",
        ["tenant", "enabled"],
        unique=False,
        postgresql_where=sa.text("enabled = TRUE"),
    )
    op.create_index(
        op.f("ix_ai_analysis_config_device_id"),
        "ai_analysis_config",
        ["device_id"],
        unique=False,
    )
    op.create_index(
        op.f("ix_ai_analysis_config_tenant"),
        "ai_analysis_config",
        ["tenant"],
        unique=False,
    )
    op.create_table(
        "anomaly_detection",
        sa.Column(
            "id", sa.UUID(), server_default=sa.text("gen_random_uuid()"), nullable=False
        ),
        sa.Column("tenant", sa.UUID(), nullable=False),
        sa.Column("device_id", sa.Integer(), nullable=False),
        sa.Column("anomaly_type", sa.Text(), nullable=False),
        sa.Column(
            "detected_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("NOW()"),
            nullable=False,
        ),
        sa.Column("period_start", sa.DateTime(timezone=True), nullable=False),
        sa.Column("period_end", sa.DateTime(timezone=True), nullable=False),
        sa.Column("expected_value", sa.Numeric(precision=15, scale=4), nullable=True),
        sa.Column("actual_value", sa.Numeric(precision=15, scale=4), nullable=False),
        sa.Column("deviation", sa.Numeric(precision=15, scale=4), nullable=True),
        sa.Column(
            "deviation_percentage", sa.Numeric(precision=8, scale=2), nullable=True
        ),
        sa.Column("algorithm_used", sa.Text(), nullable=False),
        sa.Column("confidence_score", sa.Numeric(precision=5, scale=4), nullable=True),
        sa.Column("severity", sa.Text(), server_default="medium", nullable=False),
        sa.Column(
            "details",
            postgresql.JSONB(astext_type=sa.Text()),
            server_default="{}",
            nullable=False,
        ),
        sa.Column("processed", sa.Boolean(), server_default="false", nullable=False),
        sa.Column("processed_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("alert_id", sa.UUID(), nullable=True),
        sa.Column("command_id", sa.UUID(), nullable=True),
        sa.Column("rule_id", sa.UUID(), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("NOW()"),
            nullable=False,
        ),
        sa.CheckConstraint(
            "anomaly_type IN ('spike', 'drop', 'leak', 'unusual_pattern', 'flatline', 'outlier')",
            name="chk_anomaly_type",
        ),
        sa.CheckConstraint(
            "severity IN ('low', 'medium', 'high', 'critical')",
            name="chk_anomaly_severity",
        ),
        sa.CheckConstraint(
            "confidence_score >= 0.0 AND confidence_score <= 1.0",
            name="chk_anomaly_confidence",
        ),
        sa.ForeignKeyConstraint(["device_id"], ["dim_device.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
    )
    op.create_index(
        "idx_anomaly_device_detected",
        "anomaly_detection",
        ["device_id", "detected_at"],
        unique=False,
    )
    op.create_index(
        "idx_anomaly_severity", "anomaly_detection", ["severity"], unique=False
    )
    op.create_index(
        "idx_anomaly_tenant_detected",
        "anomaly_detection",
        ["tenant", "detected_at"],
        unique=False,
    )
    op.create_index(
        "idx_anomaly_type", "anomaly_detection", ["anomaly_type"], unique=False
    )
    op.create_index(
        "idx_anomaly_unprocessed",
        "anomaly_detection",
        ["processed", "severity", "detected_at"],
        unique=False,
        postgresql_where=sa.text("processed = FALSE"),
    )
    op.create_index(
        op.f("ix_anomaly_detection_detected_at"),
        "anomaly_detection",
        ["detected_at"],
        unique=False,
    )
    op.create_index(
        op.f("ix_anomaly_detection_device_id"),
        "anomaly_detection",
        ["device_id"],
        unique=False,
    )
    op.create_index(
        op.f("ix_anomaly_detection_tenant"),
        "anomaly_detection",
        ["tenant"],
        unique=False,
    )
    op.create_table(
        "anomaly_execution",
        sa.Column(
            "id", sa.UUID(), server_default=sa.text("gen_random_uuid()"), nullable=False
        ),
        sa.Column("tenant", sa.UUID(), nullable=False),
        sa.Column("device_id", sa.Integer(), nullable=True),
        sa.Column("algorithm_type", sa.Text(), nullable=False),
        sa.Column(
            "execution_start",
            sa.DateTime(timezone=True),
            server_default=sa.text("NOW()"),
            nullable=False,
        ),
        sa.Column("execution_end", sa.DateTime(timezone=True), nullable=True),
        sa.Column("duration_seconds", sa.Numeric(precision=10, scale=3), nullable=True),
        sa.Column("period_start", sa.DateTime(timezone=True), nullable=False),
        sa.Column("period_end", sa.DateTime(timezone=True), nullable=False),
        sa.Column("status", sa.Text(), server_default="success", nullable=False),
        sa.Column(
            "anomalies_detected", sa.Integer(), server_default="0", nullable=False
        ),
        sa.Column("records_analyzed", sa.Integer(), server_default="0", nullable=False),
        sa.Column(
            "config_snapshot",
            postgresql.JSONB(astext_type=sa.Text()),
            server_default="{}",
            nullable=False,
        ),
        sa.Column("error_message", sa.Text(), nullable=True),
        sa.Column("triggered_by", sa.Text(), nullable=True),
        sa.Column("airflow_run_id", sa.Text(), nullable=True),
        sa.Column("airflow_task_id", sa.Text(), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("NOW()"),
            nullable=False,
        ),
        sa.CheckConstraint(
            "status IN ('success', 'partial', 'failed', 'skipped')",
            name="chk_anomaly_exec_status",
        ),
        sa.ForeignKeyConstraint(["device_id"], ["dim_device.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
    )
    op.create_index(
        "idx_anomaly_exec_algorithm",
        "anomaly_execution",
        ["algorithm_type", "execution_start"],
        unique=False,
    )
    op.create_index(
        "idx_anomaly_exec_status", "anomaly_execution", ["status"], unique=False
    )
    op.create_index(
        "idx_anomaly_exec_tenant_start",
        "anomaly_execution",
        ["tenant", "execution_start"],
        unique=False,
    )
    op.create_index(
        op.f("ix_anomaly_execution_device_id"),
        "anomaly_execution",
        ["device_id"],
        unique=False,
    )
    op.create_index(
        op.f("ix_anomaly_execution_tenant"),
        "anomaly_execution",
        ["tenant"],
        unique=False,
    )
    op.create_table(
        "recommendation",
        sa.Column(
            "id", sa.UUID(), server_default=sa.text("gen_random_uuid()"), nullable=False
        ),
        sa.Column("tenant", sa.UUID(), nullable=False),
        sa.Column("device_id", sa.Integer(), nullable=True),
        sa.Column("recommendation_type", sa.Text(), nullable=False),
        sa.Column(
            "generated_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("NOW()"),
            nullable=False,
        ),
        sa.Column("title", sa.Text(), nullable=False),
        sa.Column("description", sa.Text(), nullable=False),
        sa.Column(
            "estimated_savings_pct", sa.Numeric(precision=5, scale=2), nullable=True
        ),
        sa.Column(
            "estimated_savings_value", sa.Numeric(precision=10, scale=2), nullable=True
        ),
        sa.Column(
            "estimated_savings_currency", sa.Text(), server_default="PLN", nullable=True
        ),
        sa.Column("priority", sa.Text(), server_default="medium", nullable=False),
        sa.Column("confidence_score", sa.Numeric(precision=5, scale=4), nullable=True),
        sa.Column("analysis_period_start", sa.DateTime(timezone=True), nullable=False),
        sa.Column("analysis_period_end", sa.DateTime(timezone=True), nullable=False),
        sa.Column("data_points_analyzed", sa.Integer(), nullable=True),
        sa.Column(
            "details",
            postgresql.JSONB(astext_type=sa.Text()),
            server_default="{}",
            nullable=False,
        ),
        sa.Column("status", sa.Text(), server_default="pending", nullable=False),
        sa.Column("reviewed_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column("reviewed_by", sa.Text(), nullable=True),
        sa.Column("rule_id", sa.UUID(), nullable=True),
        sa.Column("expires_at", sa.DateTime(timezone=True), nullable=True),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("NOW()"),
            nullable=False,
        ),
        sa.CheckConstraint(
            "priority IN ('low', 'medium', 'high')", name="chk_rec_priority"
        ),
        sa.CheckConstraint(
            "recommendation_type IN ('optimize_heating_curve', 'reduce_night_consumption', 'balance_phases', 'adjust_schedule', 'replace_device', 'improve_power_factor', 'shift_load')",
            name="chk_rec_type",
        ),
        sa.CheckConstraint(
            "status IN ('pending', 'accepted', 'rejected', 'applied', 'expired')",
            name="chk_rec_status",
        ),
        sa.ForeignKeyConstraint(["device_id"], ["dim_device.id"], ondelete="CASCADE"),
        sa.PrimaryKeyConstraint("id"),
    )
    op.create_index(
        "idx_rec_device_generated",
        "recommendation",
        ["device_id", "generated_at"],
        unique=False,
    )
    op.create_index(
        "idx_rec_pending",
        "recommendation",
        ["status", "generated_at"],
        unique=False,
        postgresql_where=sa.text("status = 'pending'"),
    )
    op.create_index(
        "idx_rec_status_priority",
        "recommendation",
        ["status", "priority", "generated_at"],
        unique=False,
    )
    op.create_index(
        "idx_rec_tenant_generated",
        "recommendation",
        ["tenant", "generated_at"],
        unique=False,
    )
    op.create_index(
        "idx_rec_type", "recommendation", ["recommendation_type"], unique=False
    )
    op.create_index(
        op.f("ix_recommendation_device_id"),
        "recommendation",
        ["device_id"],
        unique=False,
    )
    op.create_index(
        op.f("ix_recommendation_generated_at"),
        "recommendation",
        ["generated_at"],
        unique=False,
    )
    op.create_index(
        op.f("ix_recommendation_tenant"), "recommendation", ["tenant"], unique=False
    )
    # ### end Alembic commands ###


def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_index(op.f("ix_recommendation_tenant"), table_name="recommendation")
    op.drop_index(op.f("ix_recommendation_generated_at"), table_name="recommendation")
    op.drop_index(op.f("ix_recommendation_device_id"), table_name="recommendation")
    op.drop_index("idx_rec_type", table_name="recommendation")
    op.drop_index("idx_rec_tenant_generated", table_name="recommendation")
    op.drop_index("idx_rec_status_priority", table_name="recommendation")
    op.drop_index(
        "idx_rec_pending",
        table_name="recommendation",
        postgresql_where=sa.text("status = 'pending'"),
    )
    op.drop_index("idx_rec_device_generated", table_name="recommendation")
    op.drop_table("recommendation")
    op.drop_index(op.f("ix_anomaly_execution_tenant"), table_name="anomaly_execution")
    op.drop_index(
        op.f("ix_anomaly_execution_device_id"), table_name="anomaly_execution"
    )
    op.drop_index("idx_anomaly_exec_tenant_start", table_name="anomaly_execution")
    op.drop_index("idx_anomaly_exec_status", table_name="anomaly_execution")
    op.drop_index("idx_anomaly_exec_algorithm", table_name="anomaly_execution")
    op.drop_table("anomaly_execution")
    op.drop_index(op.f("ix_anomaly_detection_tenant"), table_name="anomaly_detection")
    op.drop_index(
        op.f("ix_anomaly_detection_device_id"), table_name="anomaly_detection"
    )
    op.drop_index(
        op.f("ix_anomaly_detection_detected_at"), table_name="anomaly_detection"
    )
    op.drop_index(
        "idx_anomaly_unprocessed",
        table_name="anomaly_detection",
        postgresql_where=sa.text("processed = FALSE"),
    )
    op.drop_index("idx_anomaly_type", table_name="anomaly_detection")
    op.drop_index("idx_anomaly_tenant_detected", table_name="anomaly_detection")
    op.drop_index("idx_anomaly_severity", table_name="anomaly_detection")
    op.drop_index("idx_anomaly_device_detected", table_name="anomaly_detection")
    op.drop_table("anomaly_detection")
    op.drop_index(op.f("ix_ai_analysis_config_tenant"), table_name="ai_analysis_config")
    op.drop_index(
        op.f("ix_ai_analysis_config_device_id"), table_name="ai_analysis_config"
    )
    op.drop_index(
        "idx_ai_config_tenant_enabled",
        table_name="ai_analysis_config",
        postgresql_where=sa.text("enabled = TRUE"),
    )
    op.drop_index(
        "idx_ai_config_enabled",
        table_name="ai_analysis_config",
        postgresql_where=sa.text("enabled = TRUE"),
    )
    op.drop_index("idx_ai_config_algorithm", table_name="ai_analysis_config")
    op.drop_table("ai_analysis_config")
    op.drop_index(
        op.f("ix_recommendation_execution_tenant"),
        table_name="recommendation_execution",
    )
    op.drop_index("idx_rec_exec_tenant_start", table_name="recommendation_execution")
    op.drop_index("idx_rec_exec_status", table_name="recommendation_execution")
    op.drop_table("recommendation_execution")
    # ### end Alembic commands ###
