use Test::More;
use Test::Exception;
use strict;
use warnings;

use t::make_ini {
	ini => sub{+{
		TL => {
			trap => 'none',
		},
		DB => {
			type       => 'mysql',
			defaultset => 'SET_Default',
			SET_Default => [qw(DBCONN_test)]
		},
		DBCONN_test => {
			host   => 'localhost',
			user   => $t::make_ini::USER,
			dbname => 'test',
		},
	};},
};
use Tripletail $t::make_ini::INI_FILE;

eval { require DBD::mysql; 1; };
$@ and plan skip_all => "no DBD::mysql";

eval {
    $TL->trapError(
	-DB   => 'DB',
	-main => sub {},
       );
};
if ($@) {
	plan skip_all => "Failed to connect to local MySQL: $@";
}

plan tests => 67+24+15;

&test_mysql; #67.
&test_tx_transaction; #24.
&test_old_transaction;  #15.

sub test_mysql
{
	dies_ok {$TL->getDB} '_getInstance die';
	
	$TL->trapError(
		-DB   => 'DB',
		-main => \&main,
	);
}
sub main {

    my $DB;
	dies_ok {$TL->getDB(\123)} '_getInstance die';
	ok($DB = $TL->newDB('DB'), 'newDB');
	ok($DB->connect, 'connect');
	ok($DB->disconnect, 'disconnect');
	
    ok($DB = $TL->getDB, 'getDB');
    ok($DB = $TL->getDB('DB'), 'getDB');
    dies_ok {$DB->begin(\123)} 'getDB die';
    dies_ok {$DB->begin('getDB')} 'getDB die';

    dies_ok {$DB->rollback} 'rollback die';
    dies_ok {$DB->commit} 'commit die';
    dies_ok {$DB->unlock} 'unlock die';
    $DB->begin('SET_Default');
    dies_ok {$DB->begin('SET_Default')} 'begin die';
    $DB->commit;
    dies_ok {$DB->execute} 'execute die';
    dies_ok {$DB->execute(\123,\123)} 'execute die';
    dies_ok {$DB->execute(q{ LOCK })} 'execute die';
    dies_ok {$DB->execute(q{??})} 'execute die';
    dies_ok {$DB->execute(q{ LOCK })} 'execute die';
    dies_ok {$DB->setBufferSize(\123)} 'setBufferSize die';
    dies_ok {$DB->symquote} 'symquote die';
    dies_ok {$DB->symquote(\123)} 'symquote die';
    dies_ok {$DB->symquote('"')} 'symquote die';
	is($DB->symquote('a b c'), '`a b c`', 'symquote');

    ok($DB->begin('SET_Default'), 'begin');
    ok($DB->execute('SHOW TABLES'), 'execute');
    ok($DB->rollback, 'rollback');

	$DB->begin('SET_Default');
	# 注意: テストスクリプトを二つ同時に走らせるとおかしくなる。
	$DB->execute(q{
        DROP TABLE IF EXISTS TripletaiL_DB_Test
    });
	$DB->execute(q{
        CREATE TABLE TripletaiL_DB_Test (
            foo   BLOB,
            bar   BLOB,
            baz   BLOB
        )
    });
	$DB->commit;

    ok($DB->execute('SHOW TABLES'), 'execute w/o transaction');
    ok($DB->setDefaultSet('SET_Default'), 'setDefaultSet');
    ok($DB->execute('SHOW TABLES'), 'execute w/o transaction');

	dies_ok {$DB->execute(
		\'die' => q{
        INSERT INTO TripletaiL_DB_Test
               (foo, bar, baz)
        VALUES (?,   ?,   ?  )
    }, 'QQQ', 'WWW', 'EEE')} 'execute die';

	ok($DB->execute(
		\'SET_Default' => q{
        INSERT INTO TripletaiL_DB_Test
               (foo, bar, baz)
        VALUES (?,   ?,   ?  )
    }, 'QQQ', 'WWW', 'EEE'), 'execute with explicit DBSet');

	ok($DB->execute(q{
		SELECT *
          FROM TripletaiL_DB_Test
         LIMIT ??
    }, [1, 2, \'SQL_INTEGER']), 'execute with fully typed parameters');

#	ok($DB->execute(q{
#		SELECT *
#        FROM TripletaiL_DB_Test
#         LIMIT ??
#   }, 123), 'execute with fully typed parameters');

    dies_ok {$DB->execute(q{
		SELECT *
          FROM TripletaiL_DB_Test
	LIMIT ??
    },123)} 'execute die';

    dies_ok {$DB->execute(q{
		SELECT *
          FROM TripletaiL_DB_Test
	LIMIT ??
    },\1)} 'execute die';

    dies_ok {$DB->execute(q{
		SELECT *
          FROM TripletaiL_DB_Test
    },[\1])} 'execute die';

    dies_ok {$DB->execute(q{
		SELECT *
         FROM TripletaiL_DB_Test
	LIMIT ??
    },[])} 'execute die';

	my $insertsth;
	ok($insertsth = $DB->execute(q{
        INSERT INTO TripletaiL_DB_Test
               (foo, bar)
        VALUES (??)
    }, [1, [2, \'SQL_VARCHAR']]), 'execute with partly typed parameters');
	is($insertsth->ret, 1, 'execute return value');
	
	ok($DB->execute(q{
        INSERT INTO TripletaiL_DB_Test
               (foo, bar)
        VALUES (??)
    }, [3, [4, \'SQL_VARCHAR'], \'SQL_INTEGER']), 'execute with both partly and fully typed parameters');

	my $array;
	ok($array = $DB->selectAllHash(q{
        SELECT *
          FROM TripletaiL_DB_Test
    }), 'selectAllHash');
	is_deeply($array, [
		{foo => 'QQQ', bar => 'WWW', baz => 'EEE'},
		{foo => 1,     bar => 2,     baz => undef},
		{foo => 3,     bar => 4,     baz => undef},
	   ], 'content of selectAllHash()');

	ok($array = $DB->selectAllArray(q{
        SELECT *
          FROM TripletaiL_DB_Test
         WHERE foo = ?
    }, 'QQQ'), 'selectAllArray');
	is_deeply($array, [['QQQ', 'WWW', 'EEE']], 'content of selectAllArray()');

	is_deeply($DB->selectRowHash(q{
		SELECT *
		  FROM TripletaiL_DB_Test
	}), {foo => 'QQQ', bar => 'WWW', baz => 'EEE'}, 'selectRowHash');
	is_deeply($DB->selectRowHash(q{
		SELECT *
		  FROM TripletaiL_DB_Test
		 WHERE 0
	}), undef, 'selectRowHash, no-record becomes empty hashref');

	is_deeply($DB->selectRowArray(q{
		SELECT *
		  FROM TripletaiL_DB_Test
        }), ['QQQ', 'WWW', 'EEE'], 'selectRowArray');

	is_deeply($DB->selectRowArray(q{
		SELECT *
		  FROM TripletaiL_DB_Test
		 WHERE 0
	}), undef, 'selectRowArray, no-record becomes empty arrayref');

	ok($DB->lock(read => 'TripletaiL_DB_Test'), 'lock');
	dies_ok {$DB->lock(read => 'TripletaiL_DB_Test')} 'lock die';

	ok($DB->unlock, 'unlock');

	ok($DB->lock(set => 'SET_Default', read => 'TripletaiL_DB_Test'), 'lock with DBSet');
	$DB->unlock;

	ok($DB->setBufferSize(0), 'setBufferSize');

	is($DB->symquote('a b c'), '`a b c`', 'symquote');
	
    is($DB->getType, 'mysql', 'getType');

	is(ref($DB->getDbh), 'DBI::db', 'getDbh');

	my $sth = $DB->execute(q{
        SELECT *
          FROM TripletaiL_DB_Test
    });
	
	my $hash;
	ok($hash = $sth->fetchHash, 'fetchHash');
	is_deeply($hash, {foo => 'QQQ', bar => 'WWW', baz => 'EEE'}, 'content of fetchHash()');

	ok($array = $sth->fetchArray, 'fetchArray');
	is_deeply($array, [1, 2, undef], 'content of fetchArray()');

	1 while $sth->fetchArray;
	is($sth->rows, 3, 'rows');

	is_deeply($sth->nameArray, ['foo', 'bar', 'baz'], 'nameArray');
	is_deeply($sth->nameHash, {foo => 0, bar => 1, baz => 2}, 'nameHash');


    $DB->setBufferSize(1);

    $DB->execute(\'SET_Default' => q{
        INSERT INTO TripletaiL_DB_Test
               (foo, bar, baz)
        VALUES (?,   ?,   ?  )
    }, 'QQQQQ', 'WWWWW', 'EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE');

    $sth = $DB->execute(q{
        SELECT *
          FROM TripletaiL_DB_Test
    });
    ok($hash = $sth->fetchHash, 'fetchHash');
    is_deeply($hash, {foo => 'QQQ', bar => 'WWW', baz => 'EEE'}, 'content of fetchHash()');

    $sth = $DB->execute(q{
        SELECT *
          FROM TripletaiL_DB_Test
    });
    ok($hash = $sth->fetchArray, 'fetchArray');

    $sth = $DB->execute(q{
        SELECT *
          FROM TripletaiL_DB_Test
        WHERE foo = ?
    },'QQQQQ');
    dies_ok {$hash = $sth->fetchHash} 'fetchHash die';

    $sth = $DB->execute(q{
        SELECT *
          FROM TripletaiL_DB_Test
        WHERE foo = ?
    },'QQQQQ');
    dies_ok {$hash = $sth->fetchArray} 'fetchArray die';

	$sth->finish;

	$DB->execute(q{
        DROP TABLE TripletaiL_DB_Test
    });
}

# -----------------------------------------------------------------------------
# CREATE TABLE test_colors
# -----------------------------------------------------------------------------
sub _create_table_colors
{
	my $DB = shift;
	$DB->execute( q{
		CREATE TEMPORARY TABLE test_colors
		(
			nval INT      NOT NULL PRIMARY KEY AUTO_INCREMENT,
			sval TINYBLOB NOT NULL
		) Type=innodb
	});
	foreach my $sval (qw(blue red yellow green aqua cyan))
	{
		$DB->execute( q{
			INSERT
			  INTO test_colors (sval)
			VALUES (?)
		}, $sval);
	}
}

# -----------------------------------------------------------------------------
# test tx transaction.
# -----------------------------------------------------------------------------
sub test_tx_transaction
{
	$TL->trapError(
		-DB => 'DB',
		-main => sub{
			my $DB = $TL->getDB();
			
			# tx.
			my $tx_works;
			$DB->tx(sub{
				$tx_works = 1;
			});
			ok($tx_works, "[tx_tran] tx works");
			
			my $in_tx;
			is do{
				my $in_tx;
				$DB->tx(sub{ $in_tx = $DB->inTx(); });
			}, 1, "[tx_tran] inTx in tx";
			isnt 1, $DB->inTx(), "[tx_tran] inTx out of tx";
			
			# create test data (blue red yellow green aqua cyan)
			_create_table_colors($DB);
			is $DB->getLastInsertId(), 6, "[tx_tran] lastid";
			{
				my $s = $DB->selectAllHash("SELECT * FROM test_colors");
				is(@$s, 6, '[tx_tran] implicit commit, 6 records in tx');
				$DB->tx(sub{
					$DB->execute("DELETE FROM test_colors WHERE sval = ?", 'yellow');
					$s = $DB->selectAllHash("SELECT * FROM test_colors");
					is(@$s, 5, '[tx_tran] implicit commit, 5 records at end of tx');
				});
				$s = $DB->selectAllHash("SELECT * FROM test_colors");
				is(@$s, 5, '[tx_tran] implicit commit, 5 records after tx');
				
				$DB->tx(sub{
					$DB->execute("DELETE FROM test_colors WHERE sval = ?", 'red');
					$s = $DB->selectAllHash("SELECT * FROM test_colors");
					is(@$s, 4, '[tx_tran] explicit rollback, 4 records in tx');
					$DB->rollback;
				});
				$s = $DB->selectAllHash("SELECT * FROM test_colors");
				is(@$s, 5, '[tx_tran] explicit rollback, 5 records after tx (rollbacked)');
				
				$DB->tx(sub{
					$DB->execute("DELETE FROM test_colors WHERE sval = ?", 'red');
					$s = $DB->selectAllHash("SELECT * FROM test_colors");
					$DB->commit;
				});
				$s = $DB->selectAllHash("SELECT * FROM test_colors");
				is(@$s, 4, '[tx_tran] explicit commit');
				
				eval{ $DB->tx(sub{
					$DB->execute("DELETE FROM test_colors WHERE sval = ?", 'cyan');
					$s = $DB->selectAllHash("SELECT * FROM test_colors");
					is(@$s, 3, '[tx_tran] die implicits rollback, 3 records in tx');
					die "test\n";
				}) };
				is($@, "test\n", "[tx_tran] die in tx");
				$s = $DB->selectAllHash("SELECT * FROM test_colors");
				is(@$s, 4, '[tx_tran] die implicits rollback, 4 records after tx');
			}
			
			# close-wait.
			my $pkg = "Tripletail::DB";
			my $msg = "act something on close-wait transaction";
			foreach my $meth (qw(
				execute
				selectAllHash selectAllArray
				selectRowHash selectRowArray
			)){
				throws_ok {
					$DB->tx(sub{ $DB->commit(); $DB->$meth("SELECT 1"); })
				} qr/^$pkg#$meth, $msg\b/, "[tx_tran] execute on commit close-wait tx";
				throws_ok {
					$DB->tx(sub{ $DB->rollback(); $DB->$meth("SELECT 1"); })
				} qr/^$pkg#$meth, $msg\b/, "[tx_tran] $meth on rollback close-wait tx";
			}
		},
	);
	is($@, '', '[tx_tran] success');
}

# -----------------------------------------------------------------------------
# test old transaction.
# -----------------------------------------------------------------------------
sub test_old_transaction
{
	$TL->trapError(
		-DB => 'DB',
		-main => sub{
			my $DB = $TL->getDB();
			
			# begin and commit.
			lives_ok { $DB->begin; }    "[old_tran] begin ok";
			lives_ok { $DB->commit; }   "[old_tran] commit ok";
			
			# begin and rollback.
			lives_ok { $DB->begin; }    "[old_tran] begin ok";
			lives_ok { $DB->rollback; } "[old_tran] rollback ok";
			
			# begin tran within transaction;
			lives_ok { $DB->begin; }    "[old_tran] begin ok";
			dies_ok  { $DB->begin; }    "[old_tran] begin in tran dies";
			lives_ok { $DB->rollback; } "[old_tran] rollback ok";
			
			# begin/rollback w/o transaction.
			dies_ok { $DB->commit; }   "[old_tran] commit w/o transaction dies";
			dies_ok { $DB->rollback; } "[old_tran] rollback w/o transaction dies";
			
			# create test data.
			_create_table_colors($DB);
			
			# check whether rollback works.
			is($DB->selectRowHash(q{SELECT COUNT(*) cnt FROM test_colors})->{cnt}, 6, "[old_tran] test table contains 6 records");
			lives_ok { $DB->begin; } "[old_tran] begin";
			lives_ok { $DB->execute("DELETE FROM test_colors"); } "[old_tran] delete all";
			is($DB->selectRowHash(q{SELECT COUNT(*) cnt FROM test_colors})->{cnt}, 0, "[old_tran] test table contains no records");
			lives_ok { $DB->rollback; } "[old_tran] rollback";
			is($DB->selectRowHash(q{SELECT COUNT(*) cnt FROM test_colors})->{cnt}, 6, "[old_tran] test table contains 6 records");
		},
	);
}
