#!perl

use Modern::Perl;
use String::Util ':all';
use Data::Dumper;
use Test::More;
use feature 'state';
use File::Which;
use MySQL::Util;

use lib '.', './t';
require 'testlib.pl';

use vars qw($Util);

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

if ( !which('mysql') ) {
	plan skip_all => 'mysql not found';
}
elsif ( !check_connection() ) {
	plan skip_all => 'unable to connect to mysql';
}
else {
	drop_db();
	load_db();
	constructor();
	verify_auto_commit();
	clone_dbh();
	get_tables();
	use_db_and_get_dbname();
	get_depth();
	table_exists();
	has_ak();
	has_fks();
	get_indexes();
	get_ak_constraint();
	get_fk_constraints();
	create_data();
	drop_fks_and_apply_ddl();
	get_ak_indexes();
	get_ak_names();
	get_fk_indexes();
	get_max_depth();
	get_other_constraints();
	get_other_indexes();
	get_pk_index();
	is_column_nullable();
	drop_db();
	done_testing();
}


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

sub check_connection {

	my $mysql_cmd = get_mysql_cmdline();
	$mysql_cmd .= " -e 'show databases'";
	$mysql_cmd .= " 1>/dev/null 2>/dev/null";

	eval { system($mysql_cmd); };
	if ( $@ or $? ) {
		return 0;
	}

	return 1;
}

sub get_fk_indexes {
	my $func = ( caller(0) )[3];

	eval { $Util->get_fk_indexes; };
	ok( $@, "$func - no args" );

	my $indexes;
	ok( $indexes = $Util->get_fk_indexes('table_b'), "$func - valid table" );
}

sub get_max_depth {
	my $func = ( caller(0) )[3];

	my $depth;
	ok( $depth = $Util->get_max_depth(), "$func - call without args" );
	ok( $depth == 3, "$func - expected 3, got $depth" );
}

sub get_other_constraints {
	my $func = ( caller(0) )[3];

	eval { $Util->get_other_constraints; };
	ok( $@, "$func - no args" );

	my $cons_href;
	ok( $cons_href = $Util->get_other_constraints('table_b'),
		"$func - valid table" );
}

sub get_other_indexes {
	my $func = ( caller(0) )[3];

	eval { $Util->get_other_indexes; };
	ok( $@, "$func - no args" );

	my $indexes;
	ok( $indexes = $Util->get_other_indexes('table_b'), "$func - valid table" );
}

sub get_pk_index {
	my $func = ( caller(0) )[3];

	eval { $Util->get_pk_index };
	ok( $@, "$func - no args" );

	ok( $Util->get_pk_index('table_a'), "$func - valid table" );
}

sub is_column_nullable {
	my $func = ( caller(0) )[3];

	eval { $Util->is_column_nullable; };
	ok( $@, "$func - no args" );

	ok( $Util->is_column_nullable( table => 'table_a', column => 'tester' ),
		"$func - nullable column" );
	ok(
		!$Util->is_column_nullable(
			table  => 'table_a',
			column => 'table_a_id'
		),
		"$func - not null column"
	);
}

sub get_ak_names {
	my $func = ( caller(0) )[3];

	eval { $Util->get_ak_names; };
	ok( $@, "$func - no args" );

	my $indexes;
	ok( $indexes = $Util->get_ak_names('table_b'), "$func - valid table" );
}

sub get_ak_indexes {
	my $func = ( caller(0) )[3];

	eval { $Util->get_ak_indexes; };
	ok( $@, "$func - no args" );

	my $indexes;
	ok( $indexes = $Util->get_ak_indexes('table_b'), "$func - valid table" );

	# backwards compatible
	ok( $Util->get_ak_indexs('table_b'), "$func - backwards compatibility" );
}

sub drop_fks_and_apply_ddl {
	my $func = ( caller(0) )[3];

	my $aref = $Util->drop_fks;
	ok( ref $aref eq 'ARRAY',
		"$func - drop_fks, no args, returns an arrayref" );
	ok( scalar @$aref == 5, "$func - arrayref has correct number of elements" );

	my $href = $Util->get_fk_constraints;
	ok( ( scalar keys %$href ) == 0,
		"$func - verify no constraints in the db" );

	ok( $Util->apply_ddl($aref), "$func - apply_ddl" );

	$href = $Util->get_fk_constraints;
	ok( ( scalar keys %$href ) == 5,
		"$func - verify constraints are back in the db" );
}

sub create_data {
	my $func = ( caller(0) )[3];

	eval { $Util->create_data };
	ok( $@, "$func - called without args" );

	my $table = 'depth_0a';
	my $rows  = 20;
	ok( $Util->create_data( table => $table, rows => $rows ) == $rows,
		"$func - simple table and rows" );
	ok( get_row_cnt($table) == $rows, "$func - verify row count" );

	$table = 'depth_1a';
	$rows  = 30;
	ok( $Util->create_data( table => $table, rows => $rows ) == $rows,
		"$func - simple table and rows" );
	ok( get_row_cnt($table) == $rows, "$func - verify row count" );

	$table = 'depth_2a';
	$rows  = 40;
	eval { $Util->create_data( table => $table, rows => $rows ) };
	ok( $@, "$func - todo auto generate data for parent tables if empty" );

	$table = 'depth_0b';
	$rows  = 25;
	ok( $Util->create_data( table => $table, rows => $rows ) == $rows,
		"$func - not so simple table and rows" );
	ok( get_row_cnt($table) == $rows, "$func - verify row count" );

	# try depth_2a again
	$table = 'depth_2a';
	$rows  = 40;
	ok( $Util->create_data( table => $table, rows => $rows ) == $rows,
		"$func - complex table and rows" );
	ok( get_row_cnt($table) == $rows, "$func - verify row count" );

	$table = 'bogus';
	$rows  = 10;
	eval { $Util->create_data( table => $table, rows => $rows ); };
	ok( $@, "$func - called with non-existent table" );

	$table = 'depth_3a';
	$rows  = 50;
	ok( $Util->create_data( table => $table, rows => $rows ) == $rows,
		"$func - complex table and rows" );
	ok( get_row_cnt($table) == $rows, "$func - verify row count" );

	my $dbh = $Util->clone_dbh;
	my $sql = qq{
        select *
        from depth_0a
        };
	my $href = $dbh->selectrow_hashref($sql);
	my $id   = $href->{ uc 'depth_0a_id' };

	ok(
		$Util->create_data(
			table    => $table,
			rows     => 50,
			defaults => { depth_0a_id => $id }
		),
		"$func - create data with default hint"
	);
}

sub get_row_cnt {
	my $table = shift;

	state $dbh = $Util->clone_dbh;

	my $sql = qq{
        select count(*) from $table
    };

	return $dbh->selectrow_arrayref($sql)->[0];
}

sub get_fk_constraints {
	my $func = ( caller(0) )[3];

	eval { $Util->get_fk_constraints };
	ok( !$@, "$func - called without args" );

	# todo: validate that all fks get returned for the db

	eval { $Util->get_fk_constraints('garbage') };
	ok( $@, "$func - called with garbage" );

	my $fks;
	ok( $fks = $Util->get_fk_constraints('depth_2a'),
		"$func - called with valid table" );

	#  'depth_2a_ibfk_1' => [
	#                         {
	#                           'ORDINAL_POSITION' => '1',
	#                           'COLUMN_NAME' => 'depth_1a_id',
	#                           'REFERENCED_TABLE_NAME' => 'depth_1a',
	#                           'CONSTRAINT_SCHEMA' => 'testmysqlutil',
	#                           'REFERENCED_TABLE_SCHEMA' => 'testmysqlutil',
	#                           'CONSTRAINT_TYPE' => 'FOREIGN KEY',
	#                           'POSITION_IN_UNIQUE_CONSTRAINT' => '1',
	#                           'REFERENCED_COLUMN_NAME' => 'depth_1a_id'
	#                         }
	#                       ],
	#  'depth_2a_ibfk_2' => [
	#                         {
	#                           'ORDINAL_POSITION' => '1',
	#                           'COLUMN_NAME' => 'depth_0b_id',
	#                           'REFERENCED_TABLE_NAME' => 'depth_0b',
	#                           'CONSTRAINT_SCHEMA' => 'testmysqlutil',
	#                           'REFERENCED_TABLE_SCHEMA' => 'testmysqlutil',
	#                           'CONSTRAINT_TYPE' => 'FOREIGN KEY',
	#                           'POSITION_IN_UNIQUE_CONSTRAINT' => '1',
	#                           'REFERENCED_COLUMN_NAME' => 'depth_0b_id'
	#                         }
	#                       ]
	#};

	ok( ref $fks eq 'HASH',      "$func - result is a hashref" );
	ok( scalar keys(%$fks) == 2, "$func - result has 2 key" );

	my @constraint_names = keys %$fks;
	ok( defined $fks->{depth_2a_ibfk_1}, "$func - constraint name 1" );
	ok( defined $fks->{depth_2a_ibfk_2}, "$func - constraint name 2" );

	foreach my $constraint_name (qw (depth_2a_ibfk_1 depth_2a_ibfk_2)) {

		my $aref = $fks->{$constraint_name};
		ok( ref $aref eq 'ARRAY', "$func - columns are an arrayref" );
		ok( scalar @$aref == 1,   "$func - array elements is 1" );

		foreach my $href (@$aref) {
			my $cnt = keys %$href;
			ok( $cnt == 10, "$func - hash element column count" );
		}

		my $col = $aref->[0];
		ok( $col->{ORDINAL_POSITION} == 1, "$func - ord position" );
		ok( $col->{CONSTRAINT_SCHEMA} eq 'testmysqlutil',
			"$func - constraint schema" );
		ok( $col->{REFERENCED_TABLE_SCHEMA} eq 'testmysqlutil',
			"$func - ref table schema" );
		ok( $col->{CONSTRAINT_TYPE} eq 'FOREIGN KEY',
			"$func - constraint type" );
		ok( $col->{POSITION_IN_UNIQUE_CONSTRAINT} == 1,
			"$func - pos in uniq constraint" );
	}

	my $cname = 'depth_2a_ibfk_1';
	my $col   = $fks->{$cname}->[0];
	ok( $col->{REFERENCED_TABLE_NAME} eq 'depth_1a',
		"$func - $cname ref table name" );
	ok( $col->{COLUMN_NAME} eq 'depth_1a_id', "$func - $cname column name" );
	ok( $col->{REFERENCED_COLUMN_NAME} eq 'depth_1a_id',
		"$func - $cname ref col name" );

	$cname = 'depth_2a_ibfk_2';
	$col   = $fks->{$cname}->[0];
	ok( $col->{REFERENCED_TABLE_NAME} eq 'depth_0b',
		"$func - $cname ref table name" );
	ok( $col->{COLUMN_NAME} eq 'depth_0b_id', "$func - $cname column name" );
	ok( $col->{REFERENCED_COLUMN_NAME} eq 'depth_0b_id',
		"$func - $cname ref col name" );
}

sub get_ak_constraint {
	my $func = ( caller(0) )[3];

	eval { $Util->get_ak_constraints };
	ok( $@, "$func - called without args" );

	eval { $Util->get_ak_constraints('garbage') };
	ok( $@, "$func - called with garbage" );

	my $aks_href;
	ok( $aks_href = $Util->get_ak_constraints('table_b'),
		"$func - called with valid table" );

	ok( ref $aks_href eq 'HASH', "$func - result is a hashref" );
	ok( scalar keys(%$aks_href), "$func - result has 1 key" );

	my @names = sort keys %$aks_href;
	ok( $names[0] eq 'table_b_ak',  "$func - constraint name" );
	ok( $names[1] eq 'table_b_ak2', "$func - constraint name 2" );

	my $aref = $aks_href->{'table_b_ak'};
	ok( ref $aref eq 'ARRAY', "$func - columns are an arrayref" );
	ok( @$aref == 2,          "$func - array elements is 2" );

	foreach my $href (@$aref) {
		my $cnt = keys %$href;
		ok( $cnt == 10, "$func - array element column count" );
	}

	my $col_a = shift @$aref;
	ok( $col_a->{ORDINAL_POSITION} == 1, "$func - col_a ord position" );
	ok( !defined $col_a->{REFERENCED_TABLE_NAME},
		"$func - col_a ref table name" );
	ok( $col_a->{COLUMN_NAME} eq 'name', "$func - col_a column name" );
	ok( $col_a->{CONSTRAINT_SCHEMA} eq 'testmysqlutil',
		"$func - col_a constraint schema" );
	ok( !defined $col_a->{REFERENCED_TABLE_SCHEMA},
		"$func - col_a ref table schema" );
	ok( $col_a->{CONSTRAINT_TYPE} eq 'UNIQUE',
		"$func - col_a constraint type" );
	ok(
		!defined $col_a->{POSITION_IN_UNIQUE_CONSTRAINT},
		"$func - col_a pos in uniq constraint"
	);
	ok( !defined $col_a->{REFERENCED_COLUMN_NAME},
		"$func - col_a ref col name" );

	my $col_b = shift @$aref;
	ok( $col_b->{ORDINAL_POSITION} == 2,       "$func - col_b ord position" );
	ok( $col_b->{COLUMN_NAME} eq 'table_a_id', "$func - col_b column name" );
	ok( !defined $col_b->{REFERENCED_TABLE_NAME},
		"$func - col_b ref table name" );
	ok( $col_b->{CONSTRAINT_SCHEMA} eq 'testmysqlutil',
		"$func - col_b constraint schema" );
	ok( !defined $col_b->{REFERENCED_TABLE_SCHEMA},
		"$func - col_b ref table schema" );
	ok( $col_b->{CONSTRAINT_TYPE} eq 'UNIQUE',
		"$func - col_b constraint type" );
	ok(
		!defined $col_b->{POSITION_IN_UNIQUE_CONSTRAINT},
		"$func - col_b pos in uniq constraint"
	);
	ok( !defined $col_b->{REFERENCED_COLUMN_NAME},
		"$func - col_b ref col name" );
}

sub get_indexes {
	my $func = ( caller(0) )[3];

	eval { $Util->get_indexes };
	ok( $@, "$func - called with no args" );

	eval { $Util->get_indexes('garbage') };
	ok( $@, "$func - called with garbage table" );

	my $i_href = $Util->get_indexes('depth_2a');
	ok( scalar keys(%$i_href) == 2, "$func - index count of 2" );

	foreach my $key ( keys %$i_href ) {

		my $index_aref = $i_href->{$key};
		ok( ref $index_aref eq 'ARRAY', "$func - index is type of array" );

		foreach my $col (@$index_aref) {
			ok( scalar keys(%$col) == 13, "$func - column key count" );
		}
	}

	$i_href = $Util->get_indexes('table_d');
	ok( ref $i_href eq 'HASH',
		"$func - table without indexes returns empty hash" );
	ok( scalar keys(%$i_href) == 0, "$func - hash is empty" );

	# todo: verify some key elements
}

sub has_fks {
	my $func = ( caller(0) )[3];

	eval { $Util->has_fks };
	ok( $@, "$func - without args" );

	eval { $Util->has_fks('garbage') };
	ok( $@, "$func - with garbage arg" );

	ok( !$Util->has_fks('depth_0b'), "$func - depth_0b !has_fks" );
	ok( $Util->has_fks('depth_1a'),  "$func - depth_1a has_fks" );
	ok( $Util->has_fks('depth_2a'),  "$func - depth_2a has_fks" );
}

sub has_ak {
	my $func = ( caller(0) )[3];

	eval { $Util->has_ak };
	ok( $@, "$func - without args" );

	eval { $Util->has_ak('garbage') };
	ok( $@, "$func - with garbage arg" );

	ok( $Util->has_ak('table_a'),  "$func - table_a has_ak" );
	ok( $Util->has_ak('table_b'),  "$func - table_b has_ak" );
	ok( !$Util->has_ak('table_c'), "$func - table_c !has_ak" );
	ok( !$Util->has_ak('table_d'), "$func - table_d !has_ak" );
}

sub verify_auto_commit {
	my $func = ( caller(0) )[3];

	my $dbh  = $Util->_dbh;
	my $auto = $dbh->selectrow_arrayref('select @@autocommit')->[0];
	ok( $dbh->{AutoCommit} == $auto,
		"$func - check AutoCommit for dbd-mysql bug " );
}

sub clone_dbh {
	my $func = ( caller(0) )[3];

	my $dbh = $Util->_dbh;

	my $dbh2;
	ok( $dbh2 = $Util->clone_dbh, "$func - called without args " );
	ok(
		$dbh->{AutoCommit} == $dbh2->{AutoCommit},
		"$func - AutoCommit is identical for both handles "
	);

	my $auto = $dbh2->selectrow_arrayref('select @@autocommit')->[0];
	ok( $dbh2->{AutoCommit} == $auto,
		"$func - check AutoCommit for dbd-mysql bug " );
}

sub get_tables {
	my $func = ( caller(0) )[3];

	my $tables;
	ok( $tables = $Util->get_tables, "$func - called without args " );
	ok( scalar @$tables == 9, "$func - count " );
	ok( $tables = $Util->get_tables('garbage'), "$func - with args " );
}

sub use_db_and_get_dbname {
	my $func = ( caller(0) )[3];

	my $db = $Util->_schema;
	ok( $Util->get_dbname eq $db, "$func - called without args " );

	eval { $Util->get_dbname('garbage') };
	ok( $@, "$func - called with garbage arg " );

	my $orig_db = $Util->get_dbname;
	ok( $Util->use_db('mysql'),       "$func - use_db mysql " );
	ok( $Util->get_dbname eq 'mysql', "$func - get_dbname = mysql " );

	ok( $Util->use_db($orig_db),       "$func - use_db $orig_db" );
	ok( $Util->get_dbname eq $orig_db, "$func - get_dbname = $orig_db " );
}

sub table_exists {
	my $func = ( caller(0) )[3];

	eval { $Util->table_exists };
	ok( $@, "$func - called without args " );

	my $table = 'table_a';
	ok( $Util->table_exists($table), "$func - table $table exists " );

	$table = $Util->get_dbname . '.table_a';
	ok(
		$Util->table_exists($table), "$func -
          fq table $table exists( same db ) "
	);

	my $orig_db = $Util->get_dbname;
	$Util->use_db('mysql');
	ok(
		$Util->table_exists($table), "$func -
          fq table $table exists( mysql db ) "
	);
	$Util->use_db($orig_db);

	$table = 'garbage';
	ok( !$Util->table_exists($table), "$func - table $table does not exist " );

	$table = 'mysql.garbage';
	ok( !$Util->table_exists($table), "$func - table $table does not exist " );
}

sub get_depth {
	my $func = ( caller(0) )[3];

	eval { $Util->get_depth };
	ok( $@, "$func - called without args " );

	eval { $Util->get_depth('garbage') };
	ok( $@, "$func - called with invalid table " );

	foreach my $t ( @{ $Util->get_tables } ) {
		next unless $t =~ /^depth_(\d+)/;
		my $expected_depth = $1;

		my $d;
		eval { $d = $Util->get_depth($t) };
		ok( !$@, "$func - called with a valid table " );
		ok(
			$d == $expected_depth, "$func - depth : got = $d, expected =
          $expected_depth "
		);
	}
}

sub constructor {
	my $func = ( caller(0) )[3];
	my %conf = parse_conf();

	eval { MySQL::Util->new };
	ok( $@, "$func - no args " );

	$Util = MySQL::Util->new(
		dsn  => $conf{DBI_DSN},
		user => $conf{DBI_USER},
		pass => $conf{DBI_PASS},
		span => 0
	);
	ok( $Util, "$func - with valid args " );

	my $dbh = $Util->clone_dbh;
	$Util = MySQL::Util->new( dbh => $dbh, );
	ok( $Util, "$func - with dbh" );
}
