#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import tempfile

from cStringIO import StringIO
from sqlalchemy import MetaData, Table

from migrate.exceptions import *
from migrate.versioning.repository import Repository
from migrate.versioning import genmodel, shell, api
from migrate.tests.fixture import Shell, DB, usedb
from migrate.tests.fixture import models


class TestShellCommands(Shell):
    """Tests migrate.py commands"""

    def test_help(self):
        """Displays default help dialog"""
        self.assertEqual(self.env.run('migrate -h').returncode, 0)
        self.assertEqual(self.env.run('migrate --help').returncode, 0)
        self.assertEqual(self.env.run('migrate help').returncode, 0)

    def test_help_commands(self):
        """Display help on a specific command"""
        # we can only test that we get some output
        for cmd in api.__all__:
            result = self.env.run('migrate help %s' % cmd)
            self.assertTrue(isinstance(result.stdout, basestring))
            self.assertTrue(result.stdout)
            self.assertFalse(result.stderr)

    def test_shutdown_logging(self):
        """Try to shutdown logging output"""
        repos = self.tmp_repos()
        result = self.env.run('migrate create %s repository_name' % repos)
        result = self.env.run('migrate version %s --disable_logging' % repos)
        self.assertEqual(result.stdout, '')
        result = self.env.run('migrate version %s -q' % repos)
        self.assertEqual(result.stdout, '')

        # TODO: assert logging messages to 0
        shell.main(['version', repos], logging=False)

    def test_main_with_runpy(self):
        if sys.version_info[:2] == (2, 4):
            self.skipTest("runpy is not part of python2.4")
        from runpy import run_module
        try:
            original = sys.argv
            sys.argv=['X','--help']
            
            run_module('migrate.versioning.shell', run_name='__main__')

        finally:
            sys.argv = original

    def _check_error(self,args,code,expected,**kw):
        original = sys.stderr
        try:
            actual = StringIO()
            sys.stderr = actual
            try:
                shell.main(args,**kw)
            except SystemExit, e:
                self.assertEqual(code,e.args[0])
            else:
                self.fail('No exception raised')
        finally:
            sys.stderr = original
        actual = actual.getvalue()
        self.assertTrue(expected in actual,'%r not in:\n"""\n%s\n"""'%(expected,actual))
            
    def test_main(self):
        """Test main() function"""
        repos = self.tmp_repos()
        shell.main(['help'])
        shell.main(['help', 'create'])
        shell.main(['create', 'repo_name', '--preview_sql'], repository=repos)
        shell.main(['version', '--', '--repository=%s' % repos])
        shell.main(['version', '-d', '--repository=%s' % repos, '--version=2'])

        self._check_error(['foobar'],2,'error: Invalid command foobar')
        self._check_error(['create', 'f', 'o', 'o'],2,'error: Too many arguments for command create: o')
        self._check_error(['create'],2,'error: Not enough arguments for command create: name, repository not specified')
        self._check_error(['create', 'repo_name'],2,'already exists', repository=repos)

    def test_create(self):
        """Repositories are created successfully"""
        repos = self.tmp_repos()

        # Creating a file that doesn't exist should succeed
        result = self.env.run('migrate create %s repository_name' % repos)

        # Files should actually be created
        self.assert_(os.path.exists(repos))

        # The default table should not be None
        repos_ = Repository(repos)
        self.assertNotEquals(repos_.config.get('db_settings', 'version_table'), 'None')

        # Can't create it again: it already exists
        result = self.env.run('migrate create %s repository_name' % repos,
            expect_error=True)
        self.assertEqual(result.returncode, 2)
    
    def test_script(self):
        """We can create a migration script via the command line"""
        repos = self.tmp_repos()
        result = self.env.run('migrate create %s repository_name' % repos)

        result = self.env.run('migrate script --repository=%s Desc' % repos)
        self.assert_(os.path.exists('%s/versions/001_Desc.py' % repos))

        result = self.env.run('migrate script More %s' % repos)
        self.assert_(os.path.exists('%s/versions/002_More.py' % repos))

        result = self.env.run('migrate script "Some Random name" %s' % repos)
        self.assert_(os.path.exists('%s/versions/003_Some_Random_name.py' % repos))

    def test_script_sql(self):
        """We can create a migration sql script via the command line"""
        repos = self.tmp_repos()
        result = self.env.run('migrate create %s repository_name' % repos)

        result = self.env.run('migrate script_sql mydb foo %s' % repos)
        self.assert_(os.path.exists('%s/versions/001_foo_mydb_upgrade.sql' % repos))
        self.assert_(os.path.exists('%s/versions/001_foo_mydb_downgrade.sql' % repos))

        # Test creating a second
        result = self.env.run('migrate script_sql postgres foo --repository=%s' % repos)
        self.assert_(os.path.exists('%s/versions/002_foo_postgres_upgrade.sql' % repos))
        self.assert_(os.path.exists('%s/versions/002_foo_postgres_downgrade.sql' % repos))

        # TODO: test --previews

    def test_manage(self):
        """Create a project management script"""
        script = self.tmp_py()
        self.assert_(not os.path.exists(script))

        # No attempt is made to verify correctness of the repository path here
        result = self.env.run('migrate manage %s --repository=/bla/' % script)
        self.assert_(os.path.exists(script))


class TestShellRepository(Shell):
    """Shell commands on an existing repository/python script"""

    def setUp(self):
        """Create repository, python change script"""
        super(TestShellRepository, self).setUp()
        self.path_repos = self.tmp_repos()
        result = self.env.run('migrate create %s repository_name' % self.path_repos)

    def test_version(self):
        """Correctly detect repository version"""
        # Version: 0 (no scripts yet); successful execution
        result = self.env.run('migrate version --repository=%s' % self.path_repos)
        self.assertEqual(result.stdout.strip(), "0")

        # Also works as a positional param
        result = self.env.run('migrate version %s' % self.path_repos)
        self.assertEqual(result.stdout.strip(), "0")

        # Create a script and version should increment
        result = self.env.run('migrate script Desc %s' % self.path_repos)
        result = self.env.run('migrate version %s' % self.path_repos)
        self.assertEqual(result.stdout.strip(), "1")

    def test_source(self):
        """Correctly fetch a script's source"""
        result = self.env.run('migrate script Desc --repository=%s' % self.path_repos)

        filename = '%s/versions/001_Desc.py' % self.path_repos
        source = open(filename).read()
        self.assert_(source.find('def upgrade') >= 0)

        # Version is now 1
        result = self.env.run('migrate version %s' % self.path_repos)
        self.assertEqual(result.stdout.strip(), "1")

        # Output/verify the source of version 1
        result = self.env.run('migrate source 1 --repository=%s' % self.path_repos)
        self.assertEqual(result.stdout.strip(), source.strip())

        # We can also send the source to a file... test that too
        result = self.env.run('migrate source 1 %s --repository=%s' %
            (filename, self.path_repos))
        self.assert_(os.path.exists(filename))
        fd = open(filename)
        result = fd.read()
        self.assert_(result.strip() == source.strip())


class TestShellDatabase(Shell, DB):
    """Commands associated with a particular database"""
    # We'll need to clean up after ourself, since the shell creates its own txn;
    # we need to connect to the DB to see if things worked

    level = DB.CONNECT
        
    @usedb()
    def test_version_control(self):
        """Ensure we can set version control on a database"""
        path_repos = repos = self.tmp_repos()
        url = self.url
        result = self.env.run('migrate create %s repository_name' % repos)

        result = self.env.run('migrate drop_version_control %(url)s %(repos)s'\
            % locals(), expect_error=True)
        self.assertEqual(result.returncode, 1)
        result = self.env.run('migrate version_control %(url)s %(repos)s' % locals())

        # Clean up
        result = self.env.run('migrate drop_version_control %(url)s %(repos)s' % locals())
        # Attempting to drop vc from a database without it should fail
        result = self.env.run('migrate drop_version_control %(url)s %(repos)s'\
            % locals(), expect_error=True)
        self.assertEqual(result.returncode, 1)

    @usedb()
    def test_wrapped_kwargs(self):
        """Commands with default arguments set by manage.py"""
        path_repos = repos = self.tmp_repos()
        url = self.url
        result = self.env.run('migrate create --name=repository_name %s' % repos)
        result = self.env.run('migrate drop_version_control %(url)s %(repos)s' % locals(), expect_error=True)
        self.assertEqual(result.returncode, 1)
        result = self.env.run('migrate version_control %(url)s %(repos)s' % locals())

        result = self.env.run('migrate drop_version_control %(url)s %(repos)s' % locals())

    @usedb()
    def test_version_control_specified(self):
        """Ensure we can set version control to a particular version"""
        path_repos = self.tmp_repos()
        url = self.url
        result = self.env.run('migrate create --name=repository_name %s' % path_repos)
        result = self.env.run('migrate drop_version_control %(url)s %(path_repos)s' % locals(), expect_error=True)
        self.assertEqual(result.returncode, 1)

        # Fill the repository
        path_script = self.tmp_py()
        version = 2
        for i in range(version):
            result = self.env.run('migrate script Desc --repository=%s' % path_repos)

        # Repository version is correct
        result = self.env.run('migrate version %s' % path_repos)
        self.assertEqual(result.stdout.strip(), str(version))

        # Apply versioning to DB
        result = self.env.run('migrate version_control %(url)s %(path_repos)s %(version)s' % locals())

        # Test db version number (should start at 2)
        result = self.env.run('migrate db_version %(url)s %(path_repos)s' % locals())
        self.assertEqual(result.stdout.strip(), str(version))

        # Clean up
        result = self.env.run('migrate drop_version_control %(url)s %(path_repos)s' % locals())

    @usedb()
    def test_upgrade(self):
        """Can upgrade a versioned database"""
        # Create a repository
        repos_name = 'repos_name'
        repos_path = self.tmp()
        result = self.env.run('migrate create %(repos_path)s %(repos_name)s' % locals())
        self.assertEqual(self.run_version(repos_path), 0)

        # Version the DB
        result = self.env.run('migrate drop_version_control %s %s' % (self.url, repos_path), expect_error=True)
        result = self.env.run('migrate version_control %s %s' % (self.url, repos_path))

        # Upgrades with latest version == 0
        self.assertEqual(self.run_db_version(self.url, repos_path), 0)
        result = self.env.run('migrate upgrade %s %s' % (self.url, repos_path))
        self.assertEqual(self.run_db_version(self.url, repos_path), 0)
        result = self.env.run('migrate upgrade %s %s' % (self.url, repos_path))
        self.assertEqual(self.run_db_version(self.url, repos_path), 0)
        result = self.env.run('migrate upgrade %s %s 1' % (self.url, repos_path), expect_error=True)
        self.assertEqual(result.returncode, 1)
        result = self.env.run('migrate upgrade %s %s -1' % (self.url, repos_path), expect_error=True)
        self.assertEqual(result.returncode, 2)

        # Add a script to the repository; upgrade the db
        result = self.env.run('migrate script Desc --repository=%s' % (repos_path))
        self.assertEqual(self.run_version(repos_path), 1)
        self.assertEqual(self.run_db_version(self.url, repos_path), 0)

        # Test preview
        result = self.env.run('migrate upgrade %s %s 0 --preview_sql' % (self.url, repos_path))
        result = self.env.run('migrate upgrade %s %s 0 --preview_py' % (self.url, repos_path))

        result = self.env.run('migrate upgrade %s %s' % (self.url, repos_path))