# encoding: utf-8

from __future__ import (absolute_import, division,
                        print_function, unicode_literals)
from builtins import open, bytes

import re
import os
import glob
import hashlib
import io
from collections import namedtuple

import arrow


class Migration(namedtuple('Migration',
                           'path name is_python content checksum')):
    """
    Data class representing the specification of a migration

    Migrations can take the form of CQL files or Python scripts, and usually
    have names starting with a version string that can be ordered.
    A checksum is kept to allow detecting changes to previously applied
    migrations"""

    __slots__ = ()

    class State(object):
        """Possible states of a migration, as saved in C*"""

        SUCCEEDED = 'SUCCEEDED'
        FAILED = 'FAILED'
        SKIPPED = 'SKIPPED'
        IN_PROGRESS = 'IN_PROGRESS'

    @staticmethod
    def _natural_sort_key(s):
        """Generate a sort key for natural sorting"""
        k = tuple(int(text) if text.isdigit() else text
                  for text in re.split(r'([0-9]+)', s))
        return k

    @classmethod
    def load(cls, path):
        """Load a migration from a given file"""
        with open(path, 'r', encoding='utf-8') as fp:
            content = fp.read()

        checksum = bytes(hashlib.sha256(content.encode('utf-8')).digest())
        # Should use enum but python3 requires importing an extra library
        # Reconsidering the use of enums. This is a binary decision.
        # Boolean will work just fine.
        is_python = bool(re.findall(r"\.(py)$", os.path.abspath(path)))

        return cls(os.path.abspath(path), os.path.basename(path),
                   is_python, content, checksum)

    @classmethod
    def sort_paths(cls, paths):
        """Sort paths naturally by basename, to order by migration version"""
        return sorted(paths,
                      key=lambda p: cls._natural_sort_key(os.path.basename(p)))

    @classmethod
    def glob_all(cls, base_path, *patterns):
        """Load all paths matching a glob as migrations in sorted order"""

        paths = []
        for pattern in patterns:
            paths.extend(glob.iglob(os.path.join(base_path, pattern)))

        return list(map(cls.load, cls.sort_paths(paths)))

    @classmethod
    def generate(cls, config, description, output):
        fname_fmt = config.new_migration_name
        text_cql_fmt = config.new_cql_migration_text
        text_py_fmt = config.new_python_migration_text

        clean_desc = re.sub(r'[\W\s]+', '_', description)
        next_version = len(config.migrations) + 1
        date = arrow.utcnow()

        format_args = {
            'desc': clean_desc,
            'full_desc': description,
            'next_version': next_version,
            'date': date,
            'keyspace': config.keyspace
        }

        if output == "python":
            file_extension = ".py"
            file_content = text_py_fmt.format(**format_args)
        else:
            file_extension = ".cql"
            file_content = text_cql_fmt.format(**format_args)

        fname = fname_fmt.format(**format_args) + file_extension
        new_path = os.path.join(config.migrations_path, fname)

        cls._create_file(new_path, file_content)

        return new_path

    @classmethod
    def _create_file(cls, path, content):
        """Creates physical file"""
        with io.open(path, 'w', encoding='utf-8') as f:
            f.write(content + '\n')

    def __str__(self):
        return 'Migration("{}")'.format(self.name)