import copy
import datetime
import locale
import os
import pickle
import sys
from ._compat import PY2, string_types, pjoin, iteritems, to_bytes, exists
from ._load import portalocker
from .helpers.classes import SQLCustomType, DatabaseStoredFile


class Migrator(object):
    def __init__(self, adapter):
        self.adapter = adapter

    @property
    def db(self):
        return self.adapter.db

    @property
    def dialect(self):
        return self.adapter.dialect

    @property
    def dbengine(self):
        return self.adapter.dbengine

    def create_table(self, table, migrate=True, fake_migrate=False, polymodel=None):
        db = table._db
        table._migrate = migrate
        fields = []
        # PostGIS geo fields are added after the table has been created
        postcreation_fields = []
        sql_fields = {}
        sql_fields_aux = {}
        TFK = {}
        tablename = table._tablename
        types = self.adapter.types
        for sortable, field in enumerate(table, start=1):
            if self.db._ignore_field_case:
                field_name = field.name.lower()
                field_rname = field._rname.lower()
            else:
                field_name = field.name
                field_rname = field._rname
            if self.dbengine == "oracle":
                # Oracle needs all field names quoted to ensure consistent case
                field_rname = self.dialect.quote(field_rname)
            field_type = field.type
            if isinstance(field_type, SQLCustomType):
                ftype = field_type.native or field_type.type
            elif field_type.startswith(("reference", "big-reference")):
                if field_type.startswith("reference"):
                    referenced = field_type[10:].strip()
                    type_name = "reference"
                else:
                    referenced = field_type[14:].strip()
                    type_name = "big-reference"

                if referenced == ".":
                    referenced = tablename
                constraint_name = self.dialect.constraint_name(
                    table._raw_rname, field._raw_rname
                )
                # if not '.' in referenced \
                #         and referenced != tablename \
                #         and hasattr(table,'_primarykey'):
                #     ftype = types['integer']
                # else:
                try:
                    rtable = db[referenced]
                    rfield = rtable._id
                    rfieldname = rfield.name
                    rtablename = referenced
                except (KeyError, ValueError, AttributeError) as e:
                    self.db.logger.debug("Error: %s" % e)
                    try:
                        rtablename, rfieldname = referenced.split(".")
                        rtable = db[rtablename]
                        rfield = rtable[rfieldname]
                    except Exception as e:
                        self.db.logger.debug("Error: %s" % e)
                        raise KeyError(
                            "Cannot resolve reference %s in %s definition"
                            % (referenced, table._tablename)
                        )

                # must be PK reference or unique
                rfield_rname = rfield._rname
                if self.dbengine == "oracle":
                    rfield_rname = self.dialect.quote(rfield_rname)

                if (
                    not rfield.type.startswith(("reference", "big-reference"))
                    and getattr(rtable, "_primarykey", None)
                    and rfieldname in rtable._primarykey
                    or rfield.unique
                ):
                    ftype = types[rfield.type[:9]] % dict(length=rfield.length)
                    # multicolumn primary key reference?
                    if not rfield.unique and len(rtable._primarykey) > 1:
                        # then it has to be a table level FK
                        if rtablename not in TFK:
                            TFK[rtablename] = {}
                        TFK[rtablename][rfieldname] = field_name
                    else:
                        fk = rtable._rname + " (" + rfield._rname + ")"
                        if self.dbengine == "oracle":
                            fk = (
                                self.dialect.quote(rtable._rname)
                                + " ("
                                + rfield_rname
                                + ")"
                            )
                        ftype = ftype + types["reference FK"] % dict(
                            # should be quoted
                            constraint_name=constraint_name,
                            foreign_key=fk,
                            table_name=table._rname,
                            field_name=field._rname,
                            on_delete_action=field.ondelete,
                        )
                else:
                    # make a guess here for circular references
                    if referenced in db:
                        id_fieldname = db[referenced]._id._rname
                    elif referenced == tablename:
                        id_fieldname = table._id._rname
                    else:  # make a guess
                        id_fieldname = self.dialect.quote("id")
                    # gotcha: the referenced table must be defined before
                    # the referencing one to be able to create the table
                    # Also if it's not recommended, we can still support
                    # references to tablenames without rname to make
                    # migrations and model relationship work also if tables
                    # are not defined in order
                    if referenced == tablename:
                        real_referenced = db[referenced]._rname
                    else:
                        real_referenced = (
                            referenced in db and db[referenced]._rname or referenced
                        )
                    if self.dbengine == "oracle":
                        real_referenced = self.dialect.quote(real_referenced)
                    rfield = db[referenced]._id
                    ftype_info = dict(
                        index_name=self.dialect.quote(field._raw_rname + "__idx"),
                        field_name=field_rname,
                        constraint_name=self.dialect.quote(constraint_name),
                        foreign_key="%s (%s)" % (real_referenced, rfield_rname),
                        on_delete_action=field.ondelete,
                    )
                    ftype_info["null"] = (
                        " NOT NULL" if field.notnull else self.dialect.allow_null
                    )
                    ftype_info["unique"] = " UNIQUE" if field.unique else ""
                    ftype = types[type_name] % ftype_info
            elif field_type.startswith("list:reference"):
                ftype = types[field_type[:14]]
            elif field_type.startswith("decimal"):
                precision, scale = map(int, field_type[8:-1].split(","))
                ftype = types[field_type[:7]] % dict(precision=precision, scale=scale)
            elif field_type.startswith("geo"):
                if not hasattr(self.adapter, "srid"):
                    raise RuntimeError("Adapter does not support geometry")
                srid = self.adapter.srid
                geotype, parms = field_type[:-1].split("(")
                if geotype not in types:
                    raise SyntaxError(
                        "Field: unknown field type: %s for %s"
                        % (field_type, field_name)
                    )
                ftype = types[geotype]