import unittest

from mygrations.formats.mysql.file_reader.database import database as database_reader
from mygrations.formats.mysql.file_reader.create_parser import create_parser
class test_database_unfulfilled_fks(unittest.TestCase):
    def test_can_fulfill(self):
        """ If we have all necessary columns in all necessary tables, we can fulfill the FKs """

        strings = [
            """
            CREATE TABLE `logs` (
                `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                `message` TEXT NOT NULL,
                `traceback` text,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        """, """
            CREATE TABLE `types` (
                `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                `name` VARCHAR(255),
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        """
        ]
        db = database_reader(strings)

        new_table = create_parser()
        new_table.parse(
            """CREATE TABLE `log_changes` (
            `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `log_id` INT(10) UNSIGNED NOT NULL,
            `type_id` INT(10) UNSIGNED NOT NULL,
            `change` VARCHAR(255),
            PRIMARY KEY (id),
            KEY `log_changes_log_id` (`log_id`),
            KEY `log_changes_type_id` (`type_id`),
            CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
            CONSTRAINT `log_changes_type_id_fk` FOREIGN KEY (`type_id`) REFERENCES `types` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
            );
        """
        )

        self.assertEquals(0, len(db.unfulfilled_fks(new_table)))

    def test_can_not_fulfill(self):
        """ If we are missing even just one we can't fulfill and should get back the missing constraint """

        strings = [
            """
            CREATE TABLE `logs` (
                `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                `message` TEXT NOT NULL,
                `traceback` text,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        """
        ]
        db = database_reader(strings)

        new_table = create_parser()
        new_table.parse(
            """CREATE TABLE `log_changes` (
            `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `log_id` INT(10) UNSIGNED NOT NULL,
            `type_id` INT(10) UNSIGNED NOT NULL,
            `change` VARCHAR(255),
            PRIMARY KEY (id),
            KEY `log_changes_log_id` (`log_id`),
            KEY `log_changes_type_id` (`type_id`),
            CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
            CONSTRAINT `log_changes_type_id_fk` FOREIGN KEY (`type_id`) REFERENCES `types` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
            );
        """
        )

        errors_1215 = db.unfulfilled_fks(new_table)
        self.assertEquals(1, len(errors_1215))
        self.assertTrue('log_changes_type_id_fk' in errors_1215)
        self.assertEquals(
            'CONSTRAINT `log_changes_type_id_fk` FOREIGN KEY (`type_id`) REFERENCES `types` (`id`) ON DELETE CASCADE ON UPDATE CASCADE',
            str(errors_1215['log_changes_type_id_fk']['foreign_key'])
        )
        self.assertEquals(
            'MySQL 1215 error for foreign key `log_changes_type_id_fk`: `log_changes`.`type_id` references `types`.`id`, but table `types` does not exist',
            errors_1215['log_changes_type_id_fk']['error']
        )

    def test_missing_column(self):
        """ If we are missing even just one we can't fulfill and should get back the missing constraint """

        strings = [
            """
            CREATE TABLE `logs` (
                `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                `message` TEXT NOT NULL,
                `traceback` text,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        """
        ]
        db = database_reader(strings)

        new_table = create_parser()
        new_table.parse(
            """CREATE TABLE `log_changes` (
            `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `log_id` INT(10) UNSIGNED NOT NULL,
            `type_id` INT(10) UNSIGNED NOT NULL,
            `change` VARCHAR(255),
            PRIMARY KEY (id),
            KEY `log_changes_log_id` (`log_id`),
            KEY `log_changes_type_id` (`type_id`),
            CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
            CONSTRAINT `log_changes_type_id_fk` FOREIGN KEY (`type_id`) REFERENCES `logs` (`bob`) ON DELETE CASCADE ON UPDATE CASCADE
            );
        """
        )

        errors_1215 = db.unfulfilled_fks(new_table)
        self.assertEquals(1, len(errors_1215))
        self.assertTrue('log_changes_type_id_fk' in errors_1215)
        self.assertEquals(
            'CONSTRAINT `log_changes_type_id_fk` FOREIGN KEY (`type_id`) REFERENCES `logs` (`bob`) ON DELETE CASCADE ON UPDATE CASCADE',
            str(errors_1215['log_changes_type_id_fk']['foreign_key'])
        )
        self.assertEquals(
            'MySQL 1215 error for foreign key `log_changes_type_id_fk`: `log_changes`.`type_id` references `logs`.`bob`, but column `logs`.`bob` does not exist',
            errors_1215['log_changes_type_id_fk']['error']
        )

    def test_1215_wrong_type(self):

        strings = [
            """
            CREATE TABLE `logs` (
                `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                `message` TEXT NOT NULL,
                `traceback` text,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        """
        ]
        db = database_reader(strings)

        new_table = create_parser()
        new_table.parse(
            """CREATE TABLE `log_changes` (
            `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `log_id` BIGINT(10) UNSIGNED NOT NULL,
            `change` VARCHAR(255),
            PRIMARY KEY (id),
            KEY `log_changes_log_id` (`log_id`),
            CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
            );
        """
        )

        errors_1215 = db.unfulfilled_fks(new_table)
        self.assertEquals(1, len(errors_1215))
        self.assertTrue('log_changes_log_id_fk' in errors_1215)
        self.assertEquals(
            'CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE',
            str(errors_1215['log_changes_log_id_fk']['foreign_key'])
        )
        self.assertEquals(
            "MySQL 1215 error for foreign key `log_changes_log_id_fk`: column type mismatch. `log_changes`.`log_id` is 'BIGINT' but `logs`.`id` is 'INT'",
            errors_1215['log_changes_log_id_fk']['error']
        )

    def test_1215_wrong_length(self):

        strings = [
            """
            CREATE TABLE `logs` (
                `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                `message` TEXT NOT NULL,
                `traceback` text,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        """
        ]
        db = database_reader(strings)

        new_table = create_parser()
        new_table.parse(
            """CREATE TABLE `log_changes` (
            `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `log_id` INT(11) UNSIGNED NOT NULL,
            `change` VARCHAR(255),
            PRIMARY KEY (id),
            KEY `log_changes_log_id` (`log_id`),
            CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
            );
        """
        )

        errors_1215 = db.unfulfilled_fks(new_table)
        self.assertEquals(1, len(errors_1215))
        self.assertTrue('log_changes_log_id_fk' in errors_1215)
        self.assertEquals(
            'CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE',
            str(errors_1215['log_changes_log_id_fk']['foreign_key'])
        )
        self.assertEquals(
            "MySQL 1215 error for foreign key `log_changes_log_id_fk`: length mismatch. `log_changes`.`log_id` is '11' but `logs`.`id` is '10'",
            errors_1215['log_changes_log_id_fk']['error']
        )

    def test_1215_wrong_unsigned(self):

        strings = [
            """
            CREATE TABLE `logs` (
                `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                `message` TEXT NOT NULL,
                `traceback` text,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        """
        ]
        db = database_reader(strings)

        new_table = create_parser()
        new_table.parse(
            """CREATE TABLE `log_changes` (
            `id` INT(10) NOT NULL AUTO_INCREMENT,
            `log_id` INT(10) NOT NULL,
            `change` VARCHAR(255),
            PRIMARY KEY (id),
            KEY `log_changes_log_id` (`log_id`),
            CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
            );
        """
        )

        errors_1215 = db.unfulfilled_fks(new_table)
        self.assertEquals(1, len(errors_1215))
        self.assertTrue('log_changes_log_id_fk' in errors_1215)
        self.assertEquals(
            'CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE',
            str(errors_1215['log_changes_log_id_fk']['foreign_key'])
        )
        self.assertEquals(
            "MySQL 1215 error for foreign key `log_changes_log_id_fk`: unsigned mistmatch. `logs`.`id` is unsigned but `log_changes`.`log_id` is not",
            errors_1215['log_changes_log_id_fk']['error']
        )

    def test_1215_wrong_unsigned_reversed(self):

        strings = [
            """
            CREATE TABLE `logs` (
                `id` int(10) NOT NULL AUTO_INCREMENT,
                `message` TEXT NOT NULL,
                `traceback` text,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        """
        ]
        db = database_reader(strings)

        new_table = create_parser()
        new_table.parse(
            """CREATE TABLE `log_changes` (
            `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `log_id` INT(10) UNSIGNED NOT NULL,
            `change` VARCHAR(255),
            PRIMARY KEY (id),
            KEY `log_changes_log_id` (`log_id`),
            CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
            );
        """
        )

        errors_1215 = db.unfulfilled_fks(new_table)
        self.assertEquals(1, len(errors_1215))
        self.assertTrue('log_changes_log_id_fk' in errors_1215)
        self.assertEquals(
            'CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE',
            str(errors_1215['log_changes_log_id_fk']['foreign_key'])
        )
        self.assertEquals(
            "MySQL 1215 error for foreign key `log_changes_log_id_fk`: unsigned mistmatch. `log_changes`.`log_id` is unsigned but `logs`.`id` is not",
            errors_1215['log_changes_log_id_fk']['error']
        )

    def test_1215_on_delete_set_null_not_nullable(self):

        strings = [
            """
            CREATE TABLE `logs` (
                `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
                `message` TEXT NOT NULL,
                `traceback` text,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        """
        ]
        db = database_reader(strings)

        new_table = create_parser()
        new_table.parse(
            """CREATE TABLE `log_changes` (
            `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `log_id` INT(10) UNSIGNED NOT NULL,
            `change` VARCHAR(255),
            PRIMARY KEY (id),
            KEY `log_changes_log_id` (`log_id`),
            CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
            );
        """
        )

        errors_1215 = db.unfulfilled_fks(new_table)
        self.assertEquals(1, len(errors_1215))
        self.assertTrue('log_changes_log_id_fk' in errors_1215)
        self.assertEquals(
            'CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE SET NULL ON UPDATE CASCADE',
            str(errors_1215['log_changes_log_id_fk']['foreign_key'])
        )
        self.assertEquals(
            "MySQL 1215 error for foreign key `log_changes_log_id_fk`: invalid SET NULL. `log_changes`.`log_id` is not allowed to be null but the foreign key attempts to set the value to null ON DELETE",
            errors_1215['log_changes_log_id_fk']['error']
        )

    def test_1215_on_update_set_null_not_nullable(self):

        strings = [
            """
            CREATE TABLE `logs` (
                `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
                `message` TEXT NOT NULL,
                `traceback` text,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        """
        ]
        db = database_reader(strings)

        new_table = create_parser()
        new_table.parse(
            """CREATE TABLE `log_changes` (
            `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `log_id` INT(10) UNSIGNED NOT NULL,
            `change` VARCHAR(255),
            PRIMARY KEY (id),
            KEY `log_changes_log_id` (`log_id`),
            CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE SET NULL,
            );
        """
        )

        errors_1215 = db.unfulfilled_fks(new_table)
        self.assertEquals(1, len(errors_1215))
        self.assertTrue('log_changes_log_id_fk' in errors_1215)
        self.assertEquals(
            'CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE SET NULL',
            str(errors_1215['log_changes_log_id_fk']['foreign_key'])
        )
        self.assertEquals(
            "MySQL 1215 error for foreign key `log_changes_log_id_fk`: invalid SET NULL. `log_changes`.`log_id` is not allowed to be null but the foreign key attempts to set the value to null ON UPDATE",
            errors_1215['log_changes_log_id_fk']['error']
        )

    def test_1215_on_update_on_delete_set_null_not_nullable(self):

        strings = [
            """
            CREATE TABLE `logs` (
                `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
                `message` TEXT NOT NULL,
                `traceback` text,
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        """
        ]
        db = database_reader(strings)

        new_table = create_parser()
        new_table.parse(
            """CREATE TABLE `log_changes` (
            `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `log_id` INT(10) UNSIGNED NOT NULL,
            `change` VARCHAR(255),
            PRIMARY KEY (id),
            KEY `log_changes_log_id` (`log_id`),
            CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE SET NULL ON UPDATE SET NULL,
            );
        """
        )

        errors_1215 = db.unfulfilled_fks(new_table)
        self.assertEquals(1, len(errors_1215))
        self.assertTrue('log_changes_log_id_fk' in errors_1215)
        self.assertEquals(
            'CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE SET NULL ON UPDATE SET NULL',
            str(errors_1215['log_changes_log_id_fk']['foreign_key'])
        )
        self.assertEquals(
            "MySQL 1215 error for foreign key `log_changes_log_id_fk`: invalid SET NULL. `log_changes`.`log_id` is not allowed to be null but the foreign key attempts to set the value to null ON DELETE and ON UPDATE",
            errors_1215['log_changes_log_id_fk']['error']
        )

    def test_missing_index(self):
        """ If we have all necessary columns in all necessary tables, we can fulfill the FKs """

        strings = [
            """
            CREATE TABLE `logs` (
                `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                `message` TEXT NOT NULL,
                `traceback` text
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        """, """
            CREATE TABLE `types` (
                `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
                `name` VARCHAR(255),
                PRIMARY KEY (`id`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
        """
        ]
        db = database_reader(strings)

        new_table = create_parser()
        new_table.parse(
            """CREATE TABLE `log_changes` (
            `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
            `log_id` INT(10) UNSIGNED NOT NULL,
            `type_id` INT(10) UNSIGNED NOT NULL,
            `change` VARCHAR(255),
            PRIMARY KEY (id),
            KEY `log_changes_log_id` (`log_id`),
            KEY `log_changes_type_id` (`type_id`),
            CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
            CONSTRAINT `log_changes_type_id_fk` FOREIGN KEY (`type_id`) REFERENCES `types` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
            );
        """
        )

        errors_1215 = db.unfulfilled_fks(new_table)
        self.assertEquals(1, len(errors_1215))
        self.assertTrue('log_changes_log_id_fk' in errors_1215)
        self.assertEquals(
            'CONSTRAINT `log_changes_log_id_fk` FOREIGN KEY (`log_id`) REFERENCES `logs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE',
            str(errors_1215['log_changes_log_id_fk']['foreign_key'])
        )
        self.assertEquals(
            "MySQL 1215 error for foreign key `log_changes_log_id_fk`: missing index. `log_changes`.`log_id` references `logs`.`id` but `logs`.`id` does not have an index and therefore cannot be used in a foreign key constraint",
            errors_1215['log_changes_log_id_fk']['error']
        )
