"""initial schema — tables and sequences

Revision ID: 0001
Revises:
Create Date: 2026-06-22
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

revision = "0001"
down_revision = None
branch_labels = None
depends_on = None

def create_table_if_not_exists(table_name, *columns, **kwargs):
    conn = op.get_bind()
    inspector = sa.inspect(conn)
    
    if table_name not in inspector.get_table_names(schema="v3"):
        op.create_table(table_name, *columns, **kwargs)
        return True
    return False


def create_index_if_not_exists(index_name, table_name, columns, **kwargs):
    conn = op.get_bind()
    inspector = sa.inspect(conn)
    existing_indexes = inspector.get_indexes(table_name, schema="v3")
    
    if not any(idx['name'] == index_name for idx in existing_indexes):
        op.create_index(index_name, table_name, columns, **kwargs)
        return True
    return False


def create_check_constraint_if_not_exists(
    constraint_name, 
    table_name, 
    condition, 
    schema=None,
):
    conn = op.get_bind()
    inspector = sa.inspect(conn)
    
    existing_constraints = inspector.get_check_constraints(
        table_name, 
        schema=schema,
    )
    
    constraint_exists = any(
        c['name'] == constraint_name 
        for c in existing_constraints
    )
    
    if not constraint_exists:
        op.create_check_constraint(
            constraint_name,
            table_name,
            condition,
            schema=schema,
        )
        return True
    else:
        return False


def create_unique_constraint_if_not_exists(
    constraint_name, 
    table_name, 
    columns,
    schema=None,
):
    conn = op.get_bind()
    inspector = sa.inspect(conn)
    
    existing_constraints = inspector.get_unique_constraints(
        table_name, 
        schema=schema,
    )
    
    constraint_exists = any(
        c['name'] == constraint_name 
        for c in existing_constraints
    )
    
    if not constraint_exists:
        op.create_unique_constraint(
            constraint_name,
            table_name,
            columns,
            schema=schema,
        )
        return True
    else:
        return False


def upgrade() -> None:
    op.execute("CREATE SCHEMA IF NOT EXISTS v3")
    create_table_if_not_exists(
        'allocation',
        sa.Column('line_id', sa.Integer(),
        primary_key=True),
        sa.Column('internal_order', sa.String()),
        sa.Column('property_object', sa.String()),
        sa.Column('contract_ref', sa.String()),
        sa.Column('allocation_purpose', sa.String()),
        schema='v3',
    )

    create_table_if_not_exists(
        'app_user',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('email', sa.String(),
        nullable=False),
        sa.Column('username', sa.String(),
        nullable=False),
        sa.Column('hashed_password', sa.String(),
        nullable=False),
        sa.Column('full_name', sa.String()),
        sa.Column('role_id', sa.Integer(),
        nullable=False),
        sa.Column('is_active', sa.Boolean(),
        nullable=False),
        sa.Column('created_at', sa.DateTime(timezone=True),
        nullable=False,
        server_default=sa.func.now()),
        sa.Column('updated_at', sa.DateTime(timezone=True)),
        schema='v3',
    )

    create_index_if_not_exists(
        'ix_v4_app_user_active', 'app_user', ['is_active'],
        schema='v3',
        postgresql_where='is_active',
    )
    create_index_if_not_exists(
        'ix_v4_app_user_role', 'app_user', ['role_id'],
        schema='v3',
    )
    create_table_if_not_exists(
        'audit_log',
        sa.Column('id', sa.BigInteger(),
        primary_key=True,
        autoincrement=True),
        sa.Column('user_id', sa.Integer()),
        sa.Column('org_unit_id', sa.Integer()),
        sa.Column('task_id', sa.Integer()),
        sa.Column('form_id', sa.Integer()),
        sa.Column('event_dt', sa.DateTime(timezone=True),
        nullable=False,
        server_default=sa.func.now()),
        sa.Column('event', sa.String(),
        nullable=False),
        sa.Column('event_type', sa.String(),
        nullable=False),
        sa.Column('event_data', sa.JSON()),
        schema='v3',
    )

    create_index_if_not_exists(
        'ix_v3_audit_log_form', 'audit_log', ['form_id'],
        schema='v3',
        postgresql_where='form_id IS NOT NULL',
    )
    create_index_if_not_exists(
        'ix_v3_audit_log_event', 'audit_log', ['event'],
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v4_audit_log_dt', 'audit_log', ['event_dt'],
        postgresql_ops={'event_dt': 'DESC'},
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v4_audit_log_event', 'audit_log', ['event'],
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v4_audit_log_form', 'audit_log', ['form_id'],
        schema='v3',
        postgresql_where='form_id IS NOT NULL',
    )
    create_index_if_not_exists(
        'ix_v3_audit_log_user', 'audit_log', ['user_id'],
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v4_audit_log_user', 'audit_log', ['user_id'],
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_audit_log_dt', 'audit_log', ['event_dt'],
        postgresql_ops={'event_dt': 'DESC'},
        schema='v3',
    )
    create_table_if_not_exists(
        'booking',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('line_id', sa.Integer(),
        nullable=False),
        sa.Column('quarter', sa.SmallInteger(),
        nullable=False),
        sa.Column('source', sa.String(),
        nullable=False),
        sa.Column('booked_amount_ckk', sa.Numeric()),
        sa.Column('booked_amount_contract', sa.Numeric()),
        schema='v3',
    )

    create_check_constraint_if_not_exists(
        'ck_v3_booking_quarter', 'booking',
        sa.text('quarter BETWEEN 1 AND 4'),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'ck_v3_booking_source', 'booking',
        sa.text("source IN ('CKK','CONTRACT')"),
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_booking_line', 'booking', ['line_id'],
        schema='v3',
    )
    create_table_if_not_exists(
        'budget_form',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('form_type_code', sa.String(),
        nullable=False),
        sa.Column('year', sa.Integer()),
        sa.Column('created_by', sa.Integer()),
        sa.Column('created_at', sa.DateTime(),
            server_default=sa.func.now()
        ),
        sa.Column('updated_by', sa.Integer()),
        sa.Column('updated_at', sa.DateTime(),
            server_default=sa.func.now(),
            onupdate=sa.func.now(),
        ),
        sa.Column('org_unit_id', sa.Integer()),
        schema='v3',
    )

    create_table_if_not_exists(
        'budget_line',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('budget_form_id', sa.Integer(),
        nullable=False),
        sa.Column('expense_item_id', sa.Integer()),
        sa.Column('name', sa.String()),
        sa.Column('internal_order', sa.String()),
        sa.Column('vsp_id', sa.Integer()),
        sa.Column('project_id', sa.Integer()),
        sa.Column('justification', sa.String()),
        sa.Column('created_by', sa.Integer()),
        sa.Column('created_at', sa.DateTime()),
        sa.Column('updated_by', sa.Integer()),
        sa.Column('updated_at', sa.DateTime()),
        sa.Column('direction', sa.String()),
        schema='v3',
    )

    create_index_if_not_exists(
        'ix_v3_budget_line_vsp', 'budget_line', ['vsp_id'],
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_budget_line_form_item', 'budget_line', ['budget_form_id', 'expense_item_id'],
        schema='v3',
    )
    create_table_if_not_exists(
        'budget_line_quarter',
        sa.Column('line_id', sa.Integer(),
        primary_key=True),
        sa.Column('quarter', sa.SmallInteger(),
        primary_key=True),
        sa.Column('adj_current', sa.Numeric()),
        sa.Column('adj_ssp', sa.Numeric()),
        sa.Column('adj_rf', sa.Numeric()),
        sa.Column('adj_reserve', sa.Numeric()),
        sa.Column('adj_comment', sa.String()),
        sa.Column('target_change', sa.Numeric()),
        sa.Column('base_plan_correction', sa.Numeric()),
        sa.Column('base_plan_correction_comment', sa.String()),
        sa.Column('payment_date', sa.Date()),
        sa.Column('payment_amount', sa.Numeric()),
        sa.Column('payment_amount_ho', sa.Numeric()),
        sa.Column('payment_amount_rf', sa.Numeric()),
        sa.Column('payment_comment', sa.String()),
        sa.Column('payment_act', sa.String()),
        sa.Column('actual_m1', sa.Numeric()),
        sa.Column('actual_m2', sa.Numeric()),
        sa.Column('actual_m3', sa.Numeric()),
        sa.Column('actual_spod', sa.Numeric()),
        sa.Column('transfer_to_q2', sa.Numeric()),
        sa.Column('transfer_to_q3', sa.Numeric()),
        sa.Column('transfer_to_q4', sa.Numeric()),
        sa.Column('transfer_to_economy', sa.Numeric()),
        sa.Column('transfer_delay_acts', sa.Numeric()),
        sa.Column('transfer_delay_procurement', sa.Numeric()),
        sa.Column('transfer_economy_rf', sa.Numeric()),
        sa.Column('transfer_next_comment', sa.String()),
        sa.Column('transfer_far_comment', sa.String()),
        sa.Column('booking_amount', sa.Numeric()),
        sa.Column('plan_revision_eco_change', sa.Numeric()),
        sa.Column('plan_revision_item_adj', sa.Numeric()),
        sa.Column('plan_revision_increase', sa.Numeric()),
        sa.Column('plan_revision_sequester', sa.Numeric()),
        sa.Column('plan_revision_comment', sa.String()),
        schema='v3',
    )

    create_check_constraint_if_not_exists(
        'chk_budget_line_quarter_quarter', 'budget_line_quarter',
        sa.text('quarter >= 1 AND quarter <= 4'),
        schema='v3',
    )
    create_table_if_not_exists(
        'ckk',
        sa.Column('line_id', sa.Integer(),
        primary_key=True),
        sa.Column('ceiling_amount', sa.Numeric()),
        sa.Column('expenses_q1', sa.Numeric()),
        sa.Column('expenses_q2', sa.Numeric()),
        sa.Column('expenses_q3', sa.Numeric()),
        sa.Column('expenses_q4', sa.Numeric()),
        sa.Column('expenses_next_year_q1', sa.Numeric()),
        sa.Column('expenses_next_year_q2', sa.Numeric()),
        sa.Column('expenses_next_year_q3', sa.Numeric()),
        sa.Column('expenses_next_year_q4', sa.Numeric()),
        sa.Column('rf_schedule', sa.String()),
        sa.Column('delivery_deadline', sa.String()),
        sa.Column('procurement_plan', sa.String()),
        sa.Column('procurement_method', sa.String()),
        sa.Column('comment', sa.String()),
        schema='v3',
    )

    create_table_if_not_exists(
        'collegial_approval',
        sa.Column('line_id', sa.Integer(),
        primary_key=True),
        sa.Column('approved_amount', sa.Numeric()),
        sa.Column('protocol_reference', sa.String()),
        sa.Column('note', sa.String()),
        schema='v3',
    )

    create_table_if_not_exists(
        'contract_detail',
        sa.Column('line_id', sa.Integer(),
        primary_key=True),
        sa.Column('counterparty', sa.String()),
        sa.Column('reference', sa.String()),
        sa.Column('addenda', sa.String()),
        sa.Column('subject', sa.String()),
        sa.Column('currency', sa.String()),
        sa.Column('ceiling_amount', sa.Numeric()),
        sa.Column('expenses_q1', sa.Numeric()),
        sa.Column('expenses_q2', sa.Numeric()),
        sa.Column('expenses_q3', sa.Numeric()),
        sa.Column('expenses_q4', sa.Numeric()),
        sa.Column('rf_schedule', sa.String()),
        sa.Column('vat_rate', sa.String()),
        sa.Column('exchange_rate', sa.Numeric()),
        sa.Column('amount_foreign', sa.Numeric()),
        sa.Column('deadline', sa.String()),
        sa.Column('payment_scheme', sa.String()),
        sa.Column('act', sa.String()),
        sa.Column('comment', sa.String()),
        sa.Column('contract_date', sa.Date()),
        schema='v3',
    )

    create_table_if_not_exists(
        'contract_summary',
        sa.Column('line_id', sa.Integer(),
        primary_key=True),
        sa.Column('total_amount', sa.Numeric()),
        sa.Column('counterparty', sa.String()),
        sa.Column('reference', sa.String()),
        sa.Column('deadline', sa.String()),
        sa.Column('comment', sa.String()),
        sa.Column('future_payments_y1', sa.Numeric()),
        sa.Column('future_payments_y2', sa.Numeric()),
        sa.Column('other_ssp_amount', sa.Numeric()),
        sa.Column('centralized_flag', sa.String()),
        schema='v3',
    )

    create_table_if_not_exists(
        'expense_item',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('section_code', sa.String()),
        sa.Column('item_id', sa.String()),
        sa.Column('num_group_id', sa.String()),
        sa.Column('name', sa.String()),
        sa.Column('sheet', sa.String()),
        sa.Column('direction', sa.String()),
        sa.Column('parent_id', sa.Integer()),
        sa.Column('depth', sa.Integer()),
        schema='v3',
    )

    create_check_constraint_if_not_exists(
        'expense_item_sheet_check', 'expense_item',
        sa.text("sheet IN ('AHR', 'CAP', 'OPER')"),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'expense_item_direction_check', 'expense_item',
        sa.text("direction IN ('Support', 'Development')"),
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_expense_item_parent', 'expense_item', ['parent_id'],
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_expense_item_sheet', 'expense_item', ['sheet'],
        schema='v3',
    )
    create_table_if_not_exists(
        'form_limit',
        sa.Column('budget_form_id', sa.Integer(),
        primary_key=True),
        sa.Column('template_id', sa.Integer(),
        primary_key=True),
        sa.Column('qty_q1', sa.Integer()),
        sa.Column('qty_q2', sa.Integer()),
        sa.Column('qty_q3', sa.Integer()),
        sa.Column('qty_q4', sa.Integer()),
        sa.Column('comment', sa.String()),
        schema='v3',
    )

    create_index_if_not_exists(
        'ix_v3_form_limit_form', 'form_limit', ['budget_form_id'],
        schema='v3',
    )
    create_table_if_not_exists(
        'form_phase',
        sa.Column('budget_form_id', sa.Integer(),
        primary_key=True),
        sa.Column('sheet', sa.String(),
        primary_key=True),
        sa.Column('phase_code', sa.String(),
        primary_key=True),
        sa.Column('role', sa.String(),
        nullable=False),
        sa.Column('column_keys', postgresql.ARRAY(sa.Text()),
        nullable=False),
        sa.Column('opens_at', sa.DateTime(timezone=True),
        nullable=False),
        sa.Column('closes_at', sa.DateTime(timezone=True),
        nullable=False),
        schema='v3',
    )

    create_check_constraint_if_not_exists(
        'chk_v4_form_phase_window', 'form_phase',
        sa.text('closes_at > opens_at'),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'chk_v4_form_phase_columns', 'form_phase',
        sa.text('cardinality(column_keys) > 0'),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'chk_v4_form_phase_no_admin', 'form_phase',
        sa.text("role != 'ADMIN'"),
        schema='v3',
    )
    create_table_if_not_exists(
        'form_type',
        sa.Column('code', sa.String(),
        primary_key=True),
        sa.Column('name', sa.String()),
        sa.Column('scope', sa.String()),
        sa.Column('storage_entity', sa.String()),
        schema='v3',
    )

    create_check_constraint_if_not_exists(
        'ck_form_type_scope', 'form_type',
        sa.text("scope IN ('GO','PD','RF')"),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'ck_form_type_storage_entity', 'form_type',
        sa.text("storage_entity IN ('BUDGET_FORM','RF_PROJECT_REPORT')"),
        schema='v3',
    )
    create_table_if_not_exists(
        'limit_template',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('parent_id', sa.Integer()),
        sa.Column('sort_order', sa.Integer(),
        nullable=False),
        sa.Column('row_type', sa.String(),
        nullable=False),
        sa.Column('section_no', sa.String()),
        sa.Column('expense_item_code', sa.String()),
        sa.Column('name', sa.String(),
        nullable=False),
        sa.Column('unit', sa.String()),
        sa.Column('limit_with_vat', sa.Numeric()),
        sa.Column('limit_without_vat', sa.Numeric()),
        schema='v3',
    )

    create_check_constraint_if_not_exists(
        'ck_v3_limit_template_row_type', 'limit_template',
        sa.text("row_type IN ('SECTION','GROUP','LEAF')"),
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_limit_template_parent', 'limit_template', ['parent_id'],
        schema='v3',
    )
    create_table_if_not_exists(
        'org_unit',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('title', sa.String(),
        nullable=False),
        sa.Column('is_active', sa.Boolean(),
        nullable=False),
        sa.Column('is_ssp', sa.Boolean(),
        nullable=False),
        schema='v3',
    )

    create_index_if_not_exists(
        'ix_v4_org_unit_active', 'org_unit', ['is_active'],
        schema='v3',
        postgresql_where='is_active',    )
    create_table_if_not_exists(
        'phase_template',
        sa.Column('form_type', sa.String(),
        primary_key=True),
        sa.Column('sheet', sa.String(),
        primary_key=True),
        sa.Column('phase_code', sa.String(),
        primary_key=True),
        sa.Column('role', sa.String(),
        nullable=False),
        sa.Column('column_keys', postgresql.ARRAY(sa.Text()),
        nullable=False),
        sa.Column('opens_at', sa.DateTime(timezone=True),
        nullable=False),
        sa.Column('closes_at', sa.DateTime(timezone=True),
        nullable=False),
        schema='v3',
    )

    create_check_constraint_if_not_exists(
        'chk_v4_phase_template_columns', 'phase_template',
        sa.text('cardinality(column_keys) > 0'),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'chk_v4_phase_template_no_admin', 'phase_template',
        sa.text("role != 'ADMIN'"),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'chk_v4_phase_template_window', 'phase_template',
        sa.text('closes_at > opens_at'),
        schema='v3',
    )
    create_table_if_not_exists(
        'plan',
        sa.Column('line_id', sa.Integer(),
        primary_key=True),
        sa.Column('plan_q1', sa.Numeric()),
        sa.Column('plan_q2', sa.Numeric()),
        sa.Column('plan_q3', sa.Numeric()),
        sa.Column('plan_q4', sa.Numeric()),
        sa.Column('comment', sa.String()),
        schema='v3',
    )

    create_table_if_not_exists(
        'project',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('name', sa.String()),
        sa.Column('level', sa.String()),
        sa.Column('parent_id', sa.Integer()),
        sa.Column('project_type', sa.String()),
        sa.Column('vsp_format', sa.String()),
        sa.Column('placement_type', sa.String()),
        sa.Column('object_address', sa.String()),
        sa.Column('staff_count', sa.Integer()),
        sa.Column('total_area', sa.Numeric()),
        sa.Column('org_unit_id', sa.Integer()),
        schema='v3',
    )

    create_check_constraint_if_not_exists(
        'project_project_type_check', 'project',
        sa.text("project_type IS NULL OR project_type IN ('current', 'development')"),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'project_level_check', 'project',
        sa.text("level IN ('program', 'project')"),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'project_vsp_format_check', 'project',
        sa.text("vsp_format IS NULL OR vsp_format IN ('office', 'standalone', 'atm', 'other')"),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'project_placement_type_check', 'project',
        sa.text("placement_type IS NULL OR placement_type IN ('own', 'rent', 'other')"),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'chk_v3_project_name', 'project',
        sa.text("name IS NOT NULL AND name <> ''"),
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_project_ssp', 'project', ['org_unit_id'],
        schema='v3',
    )
    create_table_if_not_exists(
        'rent_detail',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('line_id', sa.Integer(),
        nullable=False),
        sa.Column('vsp_id', sa.Integer(),
        nullable=False),
        sa.Column('contract_number', sa.String()),
        sa.Column('contract_end_date', sa.Date()),
        sa.Column('plan_q1', sa.Numeric()),
        sa.Column('plan_q2', sa.Numeric()),
        sa.Column('plan_q3', sa.Numeric()),
        sa.Column('plan_q4', sa.Numeric()),
        sa.Column('actual_jan', sa.Numeric()),
        sa.Column('actual_feb', sa.Numeric()),
        sa.Column('actual_mar', sa.Numeric()),
        sa.Column('actual_apr', sa.Numeric()),
        sa.Column('actual_may', sa.Numeric()),
        sa.Column('actual_jun', sa.Numeric()),
        sa.Column('actual_jul', sa.Numeric()),
        sa.Column('actual_aug', sa.Numeric()),
        sa.Column('actual_sep', sa.Numeric()),
        sa.Column('actual_oct', sa.Numeric()),
        sa.Column('actual_nov', sa.Numeric()),
        sa.Column('actual_dec', sa.Numeric()),
        sa.Column('comment', sa.String()),
        schema='v3',
    )

    create_index_if_not_exists(
        'ix_v3_rent_detail_line', 'rent_detail', ['line_id'],
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_rent_detail_vsp', 'rent_detail', ['vsp_id'],
        schema='v3',
    )
    create_table_if_not_exists(
        'reserve',
        sa.Column('line_id', sa.Integer(),
        primary_key=True),
        sa.Column('amount_q1', sa.Numeric()),
        sa.Column('amount_q2', sa.Numeric()),
        sa.Column('amount_q3', sa.Numeric()),
        sa.Column('amount_q4', sa.Numeric()),
        sa.Column('justification', sa.String()),
        schema='v3',
    )

    create_table_if_not_exists(
        'rf_project_report',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('project_id', sa.Integer(),
        nullable=False),
        sa.Column('year', sa.Integer(),
        nullable=False),
        sa.Column('report_type', sa.String(),
        nullable=False),
        sa.Column('created_by', sa.Integer()),
        sa.Column('created_at', sa.DateTime(),
        server_default=sa.func.now()),
        sa.Column('updated_by', sa.Integer()),
        sa.Column('updated_at', sa.DateTime(),
        server_default=sa.func.now()),
        schema='v3',
    )

    create_index_if_not_exists(
        'ix_v3_rf_project_report_proj_year', 'rf_project_report', ['project_id', 'year'],
        schema='v3',
    )
    create_unique_constraint_if_not_exists(
        'uq_v3_rf_project_report', 'rf_project_report',
        ['project_id', 'year', 'report_type'],
        schema='v3',
    )
    create_table_if_not_exists(
        'rf_project_report_line',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('rf_project_report_id', sa.Integer(),
        nullable=False),
        sa.Column('expense_item_id', sa.Integer(),
        nullable=False),
        schema='v3',
    )

    create_index_if_not_exists(
        'ix_v3_rf_project_report_line_rep', 'rf_project_report_line', ['rf_project_report_id'],
        schema='v3',
    )
    create_unique_constraint_if_not_exists(
        'uq_v3_rf_project_report_line', 'rf_project_report_line',
        ['rf_project_report_id', 'expense_item_id'],
        schema='v3',
    )
    create_table_if_not_exists(
        'rf_project_report_quarter',
        sa.Column('rf_project_report_line_id', sa.Integer(),
        primary_key=True),
        sa.Column('quarter', sa.SmallInteger(),
        primary_key=True),
        sa.Column('adj_by_items', sa.Numeric()),
        sa.Column('adj_increase', sa.Numeric()),
        sa.Column('actual_m1', sa.Numeric()),
        sa.Column('actual_m2', sa.Numeric()),
        sa.Column('actual_m3', sa.Numeric()),
        sa.Column('actual_spod', sa.Numeric()),
        schema='v3',
    )

    create_table_if_not_exists(
        'role',
        sa.Column('id', sa.Integer(),
        primary_key=True, autoincrement=False),
        sa.Column('code', sa.String(),
        nullable=False),
        schema='v3',
    )

    create_unique_constraint_if_not_exists(
        'uq_role_code', 'role',
        ['code'],
        schema='v3',
    )
    create_table_if_not_exists(
        'form3_phase',
        sa.Column('rf_project_report_id', sa.Integer(),primary_key=True),
        sa.Column('phase_code', sa.String(), primary_key=True),
        sa.Column('role', sa.String(), nullable=False),
        sa.Column('column_keys', postgresql.ARRAY(sa.Text()), nullable=False),
        sa.Column('opens_at', sa.DateTime(timezone=True), nullable=False),
        sa.Column('closes_at', sa.DateTime(timezone=True), nullable=False),
        schema='v3',
    )

    create_check_constraint_if_not_exists(
        'chk_v3_form3_phase_window', 'form3_phase',
        sa.text('closes_at > opens_at'),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'chk_v3_form3_phase_columns', 'form3_phase',
        sa.text('cardinality(column_keys) > 0'),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'chk_v3_form3_phase_no_admin', 'form3_phase',
        sa.text("role != 'ADMIN'"),
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_form3_phase_active', 'form3_phase',
        ['rf_project_report_id', 'role', 'opens_at', 'closes_at'],
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_form3_phase_columns_gin', 'form3_phase',
        ['column_keys'],
        schema='v3',
        postgresql_using='gin',
    )
    create_table_if_not_exists(
        'security_detail',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('line_id', sa.Integer(),
        nullable=False),
        sa.Column('vsp_id', sa.Integer(),
        nullable=False),
        sa.Column('contract_number', sa.String()),
        sa.Column('contract_end_date', sa.Date()),
        sa.Column('plan_q1', sa.Numeric()),
        sa.Column('plan_q2', sa.Numeric()),
        sa.Column('plan_q3', sa.Numeric()),
        sa.Column('plan_q4', sa.Numeric()),
        sa.Column('actual_jan', sa.Numeric()),
        sa.Column('actual_feb', sa.Numeric()),
        sa.Column('actual_mar', sa.Numeric()),
        sa.Column('actual_apr', sa.Numeric()),
        sa.Column('actual_may', sa.Numeric()),
        sa.Column('actual_jun', sa.Numeric()),
        sa.Column('actual_jul', sa.Numeric()),
        sa.Column('actual_aug', sa.Numeric()),
        sa.Column('actual_sep', sa.Numeric()),
        sa.Column('actual_oct', sa.Numeric()),
        sa.Column('actual_nov', sa.Numeric()),
        sa.Column('actual_dec', sa.Numeric()),
        sa.Column('comment', sa.String()),
        schema='v3',
    )

    create_index_if_not_exists(
        'ix_v3_security_detail_vsp', 'security_detail', ['vsp_id'],
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_security_detail_line', 'security_detail', ['line_id'],
        schema='v3',
    )
    create_table_if_not_exists(
        'sequestration',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('line_id', sa.Integer(),
        nullable=False),
        sa.Column('actor', sa.String(),
        nullable=False),
        sa.Column('adj_q1', sa.Numeric()),
        sa.Column('adj_q2', sa.Numeric()),
        sa.Column('adj_q3', sa.Numeric()),
        sa.Column('adj_q4', sa.Numeric()),
        sa.Column('justification', sa.String()),
        schema='v3',
    )

    create_check_constraint_if_not_exists(
        'ck_v3_sequestration_actor', 'sequestration',
        sa.text("actor IN ('DFIP','SSP_GO')"),
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_sequestration_line_actor', 'sequestration', ['line_id', 'actor'],
        schema='v3',
    )
    create_unique_constraint_if_not_exists(
        'uq_v3_sequestration_line_actor', 'sequestration',
        ['line_id', 'actor'],
        schema='v3',
    )
    create_table_if_not_exists(
        'user_org',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('user_id', sa.Integer(),
        nullable=False),
        sa.Column('org_unit_id', sa.Integer(),
        nullable=False),
        schema='v3',
    )

    create_index_if_not_exists(
        'ix_v4_user_org_user', 'user_org', ['user_id'],
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v4_user_org_org', 'user_org', ['org_unit_id'],
        schema='v3',
    )
    create_unique_constraint_if_not_exists(
        None, 'user_org',
        ['user_id', 'org_unit_id'],
        schema='v3',
    )
    create_table_if_not_exists(
        'utility_detail',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('line_id', sa.Integer(),
        nullable=False),
        sa.Column('vsp_id', sa.Integer(),
        nullable=False),
        sa.Column('contract_number', sa.String()),
        sa.Column('contract_end_date', sa.Date()),
        sa.Column('plan_q1', sa.Numeric()),
        sa.Column('plan_q2', sa.Numeric()),
        sa.Column('plan_q3', sa.Numeric()),
        sa.Column('plan_q4', sa.Numeric()),
        sa.Column('actual_jan', sa.Numeric()),
        sa.Column('actual_feb', sa.Numeric()),
        sa.Column('actual_mar', sa.Numeric()),
        sa.Column('actual_apr', sa.Numeric()),
        sa.Column('actual_may', sa.Numeric()),
        sa.Column('actual_jun', sa.Numeric()),
        sa.Column('actual_jul', sa.Numeric()),
        sa.Column('actual_aug', sa.Numeric()),
        sa.Column('actual_sep', sa.Numeric()),
        sa.Column('actual_oct', sa.Numeric()),
        sa.Column('actual_nov', sa.Numeric()),
        sa.Column('actual_dec', sa.Numeric()),
        sa.Column('comment', sa.String()),
        schema='v3',
    )

    create_index_if_not_exists(
        'ix_v3_utility_detail_line', 'utility_detail', ['line_id'],
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_utility_detail_vsp', 'utility_detail', ['vsp_id'],
        schema='v3',
    )
    create_table_if_not_exists(
        'vsp',
        sa.Column('id', sa.Integer(),
        primary_key=True),
        sa.Column('branch_id', sa.Integer(),
        nullable=False),
        sa.Column('reg_number', sa.String()),
        sa.Column('address', sa.String()),
        sa.Column('format', sa.String()),
        sa.Column('opened_at', sa.Date()),
        sa.Column('placement_type', sa.String()),
        sa.Column('staff_count', sa.Integer()),
        sa.Column('total_area', sa.Numeric()),
        sa.Column('closed_at', sa.Date()),
        sa.Column('is_active', sa.Boolean(),
        nullable=False),
        sa.Column('is_deleted', sa.Boolean(),
        nullable=False),
        sa.Column('system_code', sa.String(100)),
        sa.Column('created_at', sa.DateTime(timezone=True),
        nullable=False,
        server_default=sa.func.now()),
        sa.Column('updated_at', sa.DateTime(timezone=True)),
        sa.Column('created_by', sa.Integer()),
        sa.Column('updated_by', sa.Integer()),
        sa.Column('vsp_type', sa.String(100)),
        sa.Column('notes', sa.Text()),
        sa.Column('location_form', sa.String()),
        sa.Column('rent_contract_num', sa.String()),
        sa.Column('rent_end_date', sa.Date()),
        schema='v3',
    )

    create_check_constraint_if_not_exists(
        'chk_closed_after_opened', 'vsp',
        sa.text('closed_at IS NULL OR opened_at IS NULL OR closed_at >= opened_at'),
        schema='v3',
    )
    create_unique_constraint_if_not_exists(
        'vsp_reg_number_unique', 'vsp',
        ['reg_number'],
        schema='v3',
    )

    create_table_if_not_exists(
        'fixed_asset_report',
        sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True),
        sa.Column('budget_form_id', sa.Integer(), nullable=False),
        sa.Column('expense_item_id', sa.Integer()),
        sa.Column('equipment_name', sa.String()),
        sa.Column('month', sa.SmallInteger(), nullable=False),
        sa.Column('opening_qty_604', sa.Integer()),
        sa.Column('opening_amt_604', sa.Numeric()),
        sa.Column('acquired_qty_604', sa.Integer()),
        sa.Column('acquired_amt_604', sa.Numeric()),
        sa.Column('disposed_qty_604', sa.Integer()),
        sa.Column('disposed_amt_604', sa.Numeric()),
        sa.Column('opening_qty_60415', sa.Integer()),
        sa.Column('opening_amt_60415', sa.Numeric()),
        sa.Column('acquired_qty_60415', sa.Integer()),
        sa.Column('acquired_amt_60415', sa.Numeric()),
        sa.Column('transferred_qty_60415', sa.Integer()),
        sa.Column('transferred_amt_60415', sa.Numeric()),
        sa.Column('go_balance_only_amt', sa.Numeric()),
        schema='v3',
    )
    create_check_constraint_if_not_exists(
        'fixed_asset_report_month_check', 'fixed_asset_report',
        sa.text('month >= 1 AND month <= 12'),
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_fixed_asset_report_form', 'fixed_asset_report', ['budget_form_id'],
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_fixed_asset_report_item', 'fixed_asset_report', ['expense_item_id'],
        schema='v3',
    )

    create_table_if_not_exists(
        'expense_item_form_type',
        sa.Column('expense_item_id', sa.Integer(), primary_key=True),
        sa.Column('form_type_code', sa.String(), primary_key=True),
        schema='v3',
    )
    create_index_if_not_exists(
        'ix_v3_eift_item', 'expense_item_form_type', ['expense_item_id'],
        schema='v3',
    )
    op.execute(
        """
            CREATE MATERIALIZED VIEW IF NOT EXISTS v3.mv_expense_item_tree
            TABLESPACE pg_default
            AS WITH RECURSIVE tree AS (
                    SELECT ei.id,
                        ei.sheet,
                        ei.section_code,
                        ei.item_id,
                        ei.num_group_id,
                        ei.name,
                        ei.parent_id,
                        ei.depth,
                        ARRAY[ei.id] AS path,
                        NULL::character varying AS parent_item_id
                    FROM v3.expense_item ei
                    WHERE ei.parent_id IS NULL
                    UNION ALL
                    SELECT ei.id,
                        ei.sheet,
                        ei.section_code,
                        ei.item_id,
                        ei.num_group_id,
                        ei.name,
                        ei.parent_id,
                        ei.depth,
                        t_1.path || ei.id,
                        t_1.item_id AS parent_item_id
                    FROM v3.expense_item ei
                        JOIN tree t_1 ON ei.parent_id = t_1.id
                    )
            SELECT id,
                sheet,
                section_code,
                item_id,
                num_group_id,
                name,
                parent_id,
                parent_item_id,
                depth,
                path,
                ARRAY( SELECT c.id
                    FROM tree c
                    WHERE t.id = ANY (c.path)) AS desc_ids
            FROM tree t
            WITH DATA;
        """
    )
    op.execute("CREATE INDEX IF NOT EXISTS  ix_v3_mv_expense_item_tree_sheet ON v3.mv_expense_item_tree USING btree (sheet);")
    op.execute("CREATE UNIQUE INDEX IF NOT EXISTS ux_v2_mv_expense_item_tree_id ON v3.mv_expense_item_tree USING btree (id);")

def downgrade() -> None:
    pass
