#!/usr/bin/perl

use lib qw(t/lib);
use strict;
use warnings;

use Test::OnlineDDL;

############################################################

my $CHUNK_SIZE = $CDTEST_MASS_POPULATE ? 5000 : 3;
my $dbms_name  = CDTest->dbms_name;

############################################################

onlineddl_test 'No-op copy' => 'Track' => sub {
    my $cd_schema  = shift;
    my $track_rsrc = $cd_schema->source('Track');
    my $dbh        = $cd_schema->storage->dbh;

    # Constructor
    my $online_ddl = DBIx::OnlineDDL->new(
        rsrc => $track_rsrc,
        # purposely not adding any (useful) coderef_hooks
        coderef_hooks => { before_triggers => sub {} },

        copy_opts => {
            chunk_size => $CHUNK_SIZE,
        },
    );

    is $online_ddl->table_name,     'track',      'Figured out table_name';
    is $online_ddl->new_table_name, '_track_new', 'Figured out new_table_name';

    my $helper = $online_ddl->_helper;
    my $mmver  = $helper->mmver;

    my $orig_table_track_sql  = $helper->create_table_sql('track');
    my $orig_table_lyrics_sql = $helper->create_table_sql('lyrics');  # has FK pointing to track

    try_ok { $online_ddl->execute } 'Execute works';

    is $online_ddl->copy_opts->{id_name}, 'trackid', 'Figured out PK';

    my $new_table_track_sql  = $helper->create_table_sql('track');
    my $new_table_lyrics_sql = $helper->create_table_sql('lyrics');

    # Remove AUTO_INCREMENT information
    $orig_table_track_sql  =~ s/ AUTO_INCREMENT=\K\d+/###/;
    $orig_table_lyrics_sql =~ s/ AUTO_INCREMENT=\K\d+/###/;
    $new_table_track_sql   =~ s/ AUTO_INCREMENT=\K\d+/###/;
    $new_table_lyrics_sql  =~ s/ AUTO_INCREMENT=\K\d+/###/;

    if ($dbms_name eq 'MySQL') {
        # This can sometimes disappear in MySQL 8, since it considers utf8mb4 the default
        $orig_table_track_sql  =~ s/ CHARACTER SET utf8mb4//g;
        $orig_table_lyrics_sql =~ s/ CHARACTER SET utf8mb4//g;
        $new_table_track_sql   =~ s/ CHARACTER SET utf8mb4//g;
        $new_table_lyrics_sql  =~ s/ CHARACTER SET utf8mb4//g;
    }

    is $new_table_track_sql,  $orig_table_track_sql,  'New table SQL for `track` matches the old one';
    SKIP: {
        skip "MySQL versions below 5.7 cannot fix the index problem", 1 if $dbms_name eq 'MySQL' && $mmver < 5.007;
        is $new_table_lyrics_sql, $orig_table_lyrics_sql, 'New table SQL for `lyrics` matches the old one';
    };
};

onlineddl_test 'Existing triggers' => 'Track' => sub {
    my $cd_schema  = shift;
    my $track_rsrc = $cd_schema->source('Track');
    my $dbh        = $cd_schema->storage->dbh;

    # Constructor (another no-op)
    my $online_ddl = DBIx::OnlineDDL->new(
        rsrc => $track_rsrc,
        coderef_hooks => { before_triggers => sub {} },
        copy_opts => {
            chunk_size => $CHUNK_SIZE,
        },
    );

    my $helper = $online_ddl->_helper;
    my $mmver  = $helper->mmver;

    # Add a few new triggers
    my (@trigger_sql, @trigger_qnames);
    foreach my $trigger_type (qw< INSERT UPDATE DELETE >) {
        my $trigger_name = $helper->find_new_trigger_identifier(
            "track_oddltest_".lc($trigger_type)
        );
        my $trigger_qname = $dbh->quote_identifier($trigger_name);
        my $table_qname   = $dbh->quote_identifier('track');

        push @trigger_sql, join("\n",
            "CREATE TRIGGER $trigger_qname BEFORE $trigger_type ON $table_qname FOR EACH ROW",
            'BEGIN',

            # SQLite doesn't like empty procedures in its triggers.  MySQL is fine with them, but doesn't like
            # returning a result set from a trigger.
            ($dbms_name eq 'SQLite' ? 'SELECT 1;' : ''),

            'END'
        );
        push @trigger_qnames, $trigger_qname;
    }

    try_ok {
        $online_ddl->dbh_runner_do(@trigger_sql);
    } 'Triggers created';

    my $should_execute = $dbms_name eq 'MySQL' && $mmver >= 5.007;

    if ($should_execute) {
        try_ok { $online_ddl->execute } 'Execute works';
    }
    else {
        like(
            dies { $online_ddl->execute },
            qr<Found conflicting triggers>,
            'Execute dies due to triggers',
        );
    }

    # Get rid of the triggers
    try_ok {
        $online_ddl->dbh_runner_do( map { "DROP TRIGGER IF EXISTS $_" } @trigger_qnames );
    } 'Triggers dropped';
};

onlineddl_test 'Add column' => 'Track' => sub {
    my $cd_schema  = shift;
    my $track_rsrc = $cd_schema->source('Track');

    # Constructor
    my $online_ddl = DBIx::OnlineDDL->new(
        rsrc => $track_rsrc,
        coderef_hooks => {
            before_triggers => sub {
                my $oddl = shift;
                my $dbh  = $oddl->dbh;
                my $name = $oddl->new_table_name;

                my $qname = $dbh->quote_identifier($name);
                my $qcol  = $dbh->quote_identifier('test_column');

                $oddl->dbh_runner_do("ALTER TABLE $qname ADD COLUMN $qcol VARCHAR(100) NULL");
            },
        },

        copy_opts => {
            chunk_size => $CHUNK_SIZE,
        },
    );

    try_ok { $online_ddl->execute } 'Execute works';

    # Verify the column exists
    my $dbh     = $cd_schema->storage->dbh;
    my $vars    = $online_ddl->_vars;
    my $catalog = $vars->{catalog};
    my $schema  = $vars->{schema};

    my %cols = %{ $dbh->column_info( $catalog, $schema, 'track', '%' )->fetchall_hashref('COLUMN_NAME') };
    like(
        $cols{test_column},
        {
            TABLE_CAT        => $catalog,
            TABLE_SCHEM      => $schema,
            TABLE_NAME       => 'track',
            COLUMN_NAME      => 'test_column',
            COLUMN_SIZE      => 100,
            TYPE_NAME        => 'VARCHAR',
            IS_NULLABLE      => 'YES',
            NULLABLE         => 1,
            ORDINAL_POSITION => 7,
        },
        'New column exists in table',
    );
};

onlineddl_test 'Add column + title change' => 'Track' => sub {
    my $cd_schema  = shift;
    my $track_rsrc = $cd_schema->source('Track');

    # Constructor
    my $online_ddl = DBIx::OnlineDDL->new(
        rsrc => $track_rsrc,
        coderef_hooks => {
            before_triggers => sub {
                my $oddl = shift;
                my $dbh  = $oddl->dbh;
                my $name = $oddl->new_table_name;

                my $qname = $dbh->quote_identifier($name);
                my $qcol  = $dbh->quote_identifier('test_column');
                my $qidx  = $dbh->quote_identifier('track_cd_title');

                $oddl->dbh_runner_do(
                    "ALTER TABLE $qname ADD COLUMN $qcol VARCHAR(100) NULL",

                    # SQLite can't DROP on an ALTER TABLE, but isn't bothered by the breaking of
                    # a unique index (for some reason)
                    ($dbms_name eq 'SQLite' ? () :
                        "ALTER TABLE $qname DROP INDEX $qidx"
                    )
                );
            },
            before_swap => sub {
                my $oddl = shift;
                my $dbh  = $oddl->dbh;
                my $name = $oddl->new_table_name;

                my $qname = $dbh->quote_identifier($name);

                DBIx::BatchChunker->construct_and_execute(
                    chunk_size => $CHUNK_SIZE,
                    process_past_max => 1,

                    dbic_storage => $oddl->rsrc->storage,
                    min_stmt   => "SELECT MIN(trackid) FROM $qname",
                    max_stmt   => "SELECT MAX(trackid) FROM $qname",
                    count_stmt => "SELECT COUNT(*) FROM $qname WHERE trackid BETWEEN ? AND ?",
                    stmt     => join( ' ',
                        'UPDATE',
                        $dbh->quote_identifier($name),
                        'SET title =',
                        $dbh->quote('This is the song that never ends'),
                        'WHERE trackid BETWEEN ? AND ?',
                    ),
                );
            },
        },

        copy_opts => {
            chunk_size => $CHUNK_SIZE,
        },
    );

    try_ok { $online_ddl->execute } 'Execute works';

    # Verify the column exists
    my $dbh     = $cd_schema->storage->dbh;
    my $vars    = $online_ddl->_vars;
    my $catalog = $vars->{catalog};
    my $schema  = $vars->{schema};

    my %cols = %{ $dbh->column_info( $catalog, $schema, 'track', '%' )->fetchall_hashref('COLUMN_NAME') };
    like(
        $cols{test_column},
        {
            TABLE_CAT        => $catalog,
            TABLE_SCHEM      => $schema,
            TABLE_NAME       => 'track',
            COLUMN_NAME      => 'test_column',
            COLUMN_SIZE      => 100,
            TYPE_NAME        => 'VARCHAR',
            IS_NULLABLE      => 'YES',
            NULLABLE         => 1,
            ORDINAL_POSITION => 7,
        },
        'New column exists in table',
    );
};

onlineddl_test 'Drop column' => 'Track' => sub {
    plan skip_all => 'SQLite cannot drop columns' if $dbms_name eq 'SQLite';

    my $cd_schema  = shift;
    my $track_rsrc = $cd_schema->source('Track');

    # Constructor
    my $online_ddl = DBIx::OnlineDDL->new(
        rsrc => $track_rsrc,
        coderef_hooks => {
            before_triggers => sub {
                my $oddl = shift;
                my $dbh  = $oddl->dbh;
                my $name = $oddl->new_table_name;

                my $qname = $dbh->quote_identifier($name);
                my $qcol  = $dbh->quote_identifier('last_updated_at');

                $oddl->dbh_runner_do("ALTER TABLE $qname DROP COLUMN $qcol");
            },
        },

        copy_opts => {
            chunk_size => $CHUNK_SIZE,
        },
    );

    try_ok { $online_ddl->execute } 'Execute works';

    # Verify the column doesn't exist
    my $dbh     = $cd_schema->storage->dbh;
    my $vars    = $online_ddl->_vars;
    my $catalog = $vars->{catalog};
    my $schema  = $vars->{schema};

    my %cols = %{ $dbh->column_info( $catalog, $schema, 'track', '%' )->fetchall_hashref('COLUMN_NAME') };

    ok(!exists $cols{last_updated_at}, 'Column dropped in table', $cols{last_updated_at});
};

onlineddl_test 'Drop PK' => 'Track' => sub {
    plan skip_all => 'SQLite cannot drop columns' if $dbms_name eq 'SQLite';

    my $cd_schema  = shift;
    my $track_rsrc = $cd_schema->source('Track');

    # Constructor
    my $online_ddl = DBIx::OnlineDDL->new(
        rsrc => $track_rsrc,
        coderef_hooks => {
            before_triggers => sub {
                my $oddl = shift;
                my $dbh  = $oddl->dbh;
                my $name = $oddl->new_table_name;

                my $qname = $dbh->quote_identifier($name);
                my $qcol  = $dbh->quote_identifier('trackid');

                $oddl->dbh_runner_do("ALTER TABLE $qname DROP COLUMN $qcol");

                my $fk_hash = $oddl->dbh_runner(run => sub {
                    # Need to also drop the FK on lyrics
                    return $oddl->_fk_info_to_hash( $oddl->_helper->foreign_key_info(
                        $oddl->_vars->{catalog}, $oddl->_vars->{schema}, $oddl->table_name,
                        undef, undef, undef
                    ) );
                });

                $oddl->dbh_runner_do(join ' ',
                    'ALTER TABLE',
                    $dbh->quote_identifier('lyrics'),
                    'DROP',
                    # MySQL uses 'FOREIGN KEY' on DROPs, and everybody else uses 'CONSTRAINT' on both
                    ($dbms_name eq 'MySQL' ? 'FOREIGN KEY' : 'CONSTRAINT'),
                    $dbh->quote_identifier( (values %$fk_hash)[0]->{fk_name} ),
                );
            },
        },

        copy_opts => {
            chunk_size => $CHUNK_SIZE,
        },
    );

    try_ok { $online_ddl->execute } 'Execute works';

    # Verify the column doesn't exist
    my $dbh     = $cd_schema->storage->dbh;
    my $vars    = $online_ddl->_vars;
    my $catalog = $vars->{catalog};
    my $schema  = $vars->{schema};

    my %cols = %{ $dbh->column_info( $catalog, $schema, 'track', '%' )->fetchall_hashref('COLUMN_NAME') };

    ok(!exists $cols{trackid}, 'PK column dropped in table', $cols{trackid});
};

############################################################

done_testing;
