from alembic import op
import sqlalchemy as sa


revision = "0005"
down_revision = "0004"
branch_labels = None
depends_on = None


def upgrade() -> None:
    op.execute('ALTER TABLE v3.phase_template ALTER COLUMN opens_at TYPE timestamp USING opens_at::timestamp')
    op.execute('ALTER TABLE v3.phase_template ALTER COLUMN closes_at TYPE timestamp USING closes_at::timestamp')
    op.execute("ALTER TABLE v3.org_unit ADD COLUMN utc_offset VARCHAR(3) DEFAULT '+03' NOT NULL")
    # заводим фазу с нужной таймзоной
    op.execute(
    """
        CREATE OR REPLACE FUNCTION v3.copy_template_to_form(p_form_id integer)
        RETURNS integer
        LANGUAGE plpgsql
        AS $function$
        DECLARE
            v_form_type VARCHAR;
            v_inserted  INTEGER;
            v_utc_offset VARCHAR(3);
            v_org_unit INTEGER;
        BEGIN
            SELECT form_type_code, org_unit_id INTO v_form_type, v_org_unit
            FROM v3.budget_form
            WHERE id = p_form_id;

            IF NOT FOUND THEN
                RAISE EXCEPTION 'budget_form id=% не существует', p_form_id;
            END IF;

            SELECT utc_offset INTO v_utc_offset
                FROM v3.org_unit
                WHERE id = v_org_unit;
            
            INSERT INTO v3.form_phase
                (budget_form_id, sheet, phase_code, role, column_keys, opens_at, closes_at)
            SELECT p_form_id, pt.sheet, pt.phase_code, pt.role,
                pt.column_keys, (pt.opens_at::text || v_utc_offset)::timestamptz, 
                (pt.closes_at::text || v_utc_offset)::timestamptz
            FROM v3.phase_template pt
            WHERE pt.form_type = v_form_type
            ON CONFLICT (budget_form_id, sheet, phase_code) DO NOTHING;

            GET DIAGNOSTICS v_inserted = ROW_COUNT;
            RETURN v_inserted;
        END;
        $function$
        ;
    """
    )
    # Создавая фазу добавляем таймзону орг юнита
    op.execute(
        """
            CREATE OR REPLACE FUNCTION v3.add_form_phase(p_budget_form_id integer, p_sheet character varying, p_phase_code character varying, p_role character varying, p_column_keys text[], p_opens_at timestamp, p_closes_at timestamp)
            RETURNS v3.form_phase
            LANGUAGE plpgsql
            AS $function$
            DECLARE
                v_phase v3.form_phase;
                v_org_unit_id INT;
                v_org_unit_title VARCHAR;
                v_form_type_code VARCHAR;
                v_year INT;
                v_task_name TEXT;
                v_phase_id TEXT;
                v_utc_offset VARCHAR(3);
            BEGIN
                SELECT bf.org_unit_id, ou.title, bf.form_type_code, bf.year
                INTO v_org_unit_id, v_org_unit_title, v_form_type_code, v_year
                FROM v3.budget_form bf
                LEFT JOIN v3.org_unit ou ON ou.id = bf.org_unit_id
                WHERE bf.id = p_budget_form_id;
                
                SELECT utc_offset INTO v_utc_offset
                FROM v3.org_unit
                WHERE id = v_org_unit_id;
                
                INSERT INTO v3.form_phase(
                    budget_form_id, sheet, phase_code, role, column_keys, opens_at, closes_at
                )
                VALUES (
                    p_budget_form_id, 
                    p_sheet, 
                    p_phase_code, 
                    p_role, 
                    p_column_keys, 
                    CASE WHEN p_opens_at IS NOT NULL THEN (p_opens_at::text || v_utc_offset)::timestamptz ELSE NULL END,
                    CASE WHEN p_closes_at IS NOT NULL THEN (p_closes_at::text || v_utc_offset)::timestamptz ELSE NULL END
                    
                )
                RETURNING * INTO v_phase;

                
                v_task_name := trim(concat(
                    COALESCE(v_form_type_code, ''),
                    CASE WHEN v_year IS NOT NULL THEN ' ' || v_year::TEXT ELSE '' END,
                    CASE WHEN v_org_unit_title IS NOT NULL THEN ' [' || v_org_unit_title || ']' ELSE '' END
                ));
                v_phase_id := format('%s:%s:%s', p_budget_form_id, p_sheet, p_phase_code);

                PERFORM v3.log_event(
                    'ACCESS_WINDOW_CHANGE', 'ACCESS',
                    jsonb_build_object(
                        'entity_type', 'form_phase',
                        'entity_id', p_budget_form_id,
                        'action', 'create',
                        'phase_id', v_phase_id,
                        'phase_name', p_phase_code,
                        'task_id', p_budget_form_id,
                        'form_id', p_budget_form_id,
                        'budget_form_id', p_budget_form_id,
                        'org_unit_id', v_org_unit_id,
                        'org_unit_name', v_org_unit_title,
                        'sheet', p_sheet,
                        'phase_code', p_phase_code,
                        'role', v_phase.role,
                        'column_keys', v_phase.column_keys,
                        'opens_at', v_phase.opens_at,
                        'closes_at', v_phase.closes_at
                    ),
                    p_budget_form_id, null, v_org_unit_id
                );

                RETURN v_phase;
            END;
            $function$
            ;
        """
    )
    # Обнволяя фазу добавляем таймзону орг юнита
    op.execute(
        """
            CREATE OR REPLACE FUNCTION v3.upd_form_phase(p_budget_form_id integer, p_sheet character varying, p_phase_code character varying, p_role character varying DEFAULT NULL::character varying, p_column_keys text[] DEFAULT NULL::text[], p_opens_at timestamp without time zone DEFAULT NULL::timestamp without time zone, p_closes_at timestamp without time zone DEFAULT NULL::timestamp without time zone)
            RETURNS v3.form_phase
            LANGUAGE plpgsql
            AS $function$
            DECLARE
                v_old v3.form_phase;
                v_new v3.form_phase;
                v_is_extend BOOLEAN := FALSE;
                v_has_non_extend_change BOOLEAN := FALSE;
                v_has_window_change BOOLEAN := FALSE;
                v_changes JSONB := '{}'::jsonb;
                v_org_unit_id INT;
                v_org_unit_title VARCHAR;
                v_form_type_code VARCHAR;
                v_year INT;
                v_task_name TEXT;
                v_phase_id TEXT;
                v_utc_offset VARCHAR(3);
                v_opens_at timestamptz;
                v_closes_at timestamptz;
            BEGIN
                SELECT * INTO v_old
                FROM v3.form_phase
                WHERE budget_form_id = p_budget_form_id
                AND sheet = p_sheet
                AND phase_code = p_phase_code;

                IF NOT FOUND THEN
                    RAISE EXCEPTION 'form_phase не найден (form_id=%, sheet=%, phase=%)',
                    p_budget_form_id, p_sheet, p_phase_code;
                END IF;

                SELECT bf.org_unit_id, ou.title, bf.form_type_code, bf.year, ou.utc_offset
                INTO v_org_unit_id, v_org_unit_title, v_form_type_code, v_year, v_utc_offset
                FROM v3.budget_form bf
                LEFT JOIN v3.org_unit ou ON ou.id = bf.org_unit_id
                WHERE bf.id = p_budget_form_id;
                v_task_name := trim(concat(
                    COALESCE(v_form_type_code, ''),
                    CASE WHEN v_year IS NOT NULL THEN ' ' || v_year::TEXT ELSE '' END,
                    CASE WHEN v_org_unit_title IS NOT NULL THEN ' [' || v_org_unit_title || ']' ELSE '' END
                ));

                IF p_opens_at is not null THEN
                    v_opens_at = (p_opens_at::text || v_utc_offset)::timestamptz;
                END IF;
                IF p_closes_at is not null THEN
                    v_closes_at = (p_closes_at::text || v_utc_offset)::timestamptz;
                END IF;

                UPDATE v3.form_phase
                SET role        = COALESCE(p_role, role),
                    column_keys = COALESCE(p_column_keys, column_keys),
                    opens_at    = COALESCE(v_opens_at, opens_at),
                    closes_at   = COALESCE(v_closes_at, closes_at)
                WHERE budget_form_id = p_budget_form_id
                AND sheet = p_sheet
                AND phase_code = p_phase_code
                RETURNING * INTO v_new;

                
                v_phase_id := format('%s:%s:%s', p_budget_form_id, p_sheet, p_phase_code);

                v_is_extend := v_new.closes_at > v_old.closes_at;
                v_has_non_extend_change := v_old.role IS DISTINCT FROM v_new.role
                    OR v_old.column_keys IS DISTINCT FROM v_new.column_keys
                    OR v_old.opens_at IS DISTINCT FROM v_new.opens_at;
                v_has_window_change := v_has_non_extend_change
                    OR (v_old.closes_at IS DISTINCT FROM v_new.closes_at AND NOT v_is_extend);

                IF v_old.role IS DISTINCT FROM v_new.role THEN
                    v_changes := v_changes || jsonb_build_object(
                        'role', jsonb_build_object('before', v_old.role, 'after', v_new.role)
                    );
                END IF;
                IF v_old.column_keys IS DISTINCT FROM v_new.column_keys THEN
                    v_changes := v_changes || jsonb_build_object(
                        'column_keys', jsonb_build_object('before', v_old.column_keys, 'after', v_new.column_keys)
                    );
                END IF;
                IF v_old.opens_at IS DISTINCT FROM v_new.opens_at THEN
                    v_changes := v_changes || jsonb_build_object(
                        'opens_at', jsonb_build_object(
                            'before', v_old.opens_at,
                            'after', v_new.opens_at
                        )
                    );
                END IF;
                IF v_old.closes_at IS DISTINCT FROM v_new.closes_at THEN
                    v_changes := v_changes || jsonb_build_object(
                        'closes_at', jsonb_build_object(
                            'before', v_old.closes_at,
                            'after', v_new.closes_at
                        )
                    );
                END IF;

                IF v_is_extend THEN
                    PERFORM v3.log_event(
                        'ACCESS_EXTEND', 'ACCESS',
                        jsonb_build_object(
                            'phase_id', v_phase_id,
                            'phase_name', p_phase_code,
                            'task_id', p_budget_form_id,
                            'task_name', NULLIF(v_task_name, ''),
                            'form_id', p_budget_form_id,
                            'budget_form_id', p_budget_form_id,
                            'org_unit_id', v_org_unit_id,
                            'org_unit_name', v_org_unit_title,
                            'sheet', p_sheet,
                            'phase_code', p_phase_code,
                            'closes_at_before', v_old.closes_at,
                            'closes_at_after', v_new.closes_at
                        ),
                        p_budget_form_id
                    );
                END IF;

                IF v_has_window_change THEN
                    PERFORM v3.log_event(
                        'ACCESS_WINDOW_CHANGE', 'ACCESS',
                        jsonb_build_object(
                            'entity_type', 'form_phase',
                            'entity_id', p_budget_form_id,
                            'action', 'update',
                            'phase_id', v_phase_id,
                            'phase_name', p_phase_code,
                            'task_id', p_budget_form_id,
                            'task_name', NULLIF(v_task_name, ''),
                            'form_id', p_budget_form_id,
                            'budget_form_id', p_budget_form_id,
                            'org_unit_id', v_org_unit_id,
                            'org_unit_name', v_org_unit_title,
                            'sheet', p_sheet,
                            'phase_code', p_phase_code,
                            'changes', v_changes
                        ),
                        p_budget_form_id, null, v_org_unit_id
                    );
                END IF;

                RETURN v_new;
            END;
            $function$
            ;
        """
    )
    
    # при обновлении таймзоны орг юнита обновляем актуальные фазы, связанные с ними
    op.execute(
        """
            CREATE OR REPLACE FUNCTION v3.upd_phases_timezones(p_org_unit_id integer, p_old_tz varchar(3), p_new_tz varchar(3))
            RETURNS integer
            LANGUAGE plpgsql
            AS $function$
            DECLARE
                v_result integer;
            BEGIN
                IF p_old_tz = p_new_tz THEN
                    RETURN 0;
                END IF;
                WITH updated AS (
                    UPDATE v3.form_phase
                    SET opens_at    = ((opens_at AT TIME zone (p_old_tz || '::00')::interval)::text || p_new_tz)::timestamptz,
                        closes_at   = ((closes_at AT TIME zone (p_old_tz || '::00')::interval)::text || p_new_tz)::timestamptz
                    WHERE budget_form_id IN (SELECT id FROM v3.budget_form WHERE org_unit_id = p_org_unit_id)
                    AND (
                            opens_at >= CURRENT_TIMESTAMP
                            OR closes_at >= CURRENT_TIMESTAMP
                        )
                    RETURNING *
                )
                SELECT COUNT(*) INTO v_result FROM updated;
                RETURN v_result;
                
            END;
            $function$
            ;
        """
    )
    # op.add_column(
    #     'org_unit',
    #     sa.Column(
    #         'utc_offset',
    #         sa.String(3),
    #         nullable=False,
    #         server_default='+03',
    #     )
    # )


def downgrade() -> None:
    pass
