from parsons.databases.database.database import DatabaseCreateStatement
import parsons.databases.postgres.constants as consts

import petl
import logging

logger = logging.getLogger(__name__)


class PostgresCreateStatement(DatabaseCreateStatement):
    def __init__(self):
        super().__init__()

        self.COL_NAME_MAX_LEN = consts.COL_NAME_MAX_LEN
        self.REPLACE_CHARS = consts.REPLACE_CHARS

        # Postgres doesn't have a medium int
        self.MEDIUMINT = self.INT

        # Currently py floats are coded as Postgres decimals
        self.FLOAT = consts.DECIMAL

        # Max length of a Redshift VARCHAR column
        self.VARCHAR_MAX = consts.VARCHAR_MAX

        # List of varchar lengths to use for columns -- this list needs to be in order from
        # smallest to largest
        self.VARCHAR_STEPS = consts.VARCHAR_STEPS

    # the default behavior is f"{col}_"
    def _rename_reserved_word(self, col, index):
        """Return the renamed column.

        `Args`:
            col: str
                The column to rename.
            index: int
                (Optional) The index of the column.
        `Returns`:
            str
                The rename column.
        """
        return f"col_{index}"

    def create_statement(
        self,
        tbl,
        table_name,
        padding=None,
        distkey=None,
        sortkey=None,
        varchar_max=None,
        varchar_truncate=True,
        columntypes=None,
        strict_length=True,
    ):
        # Generate a table create statement. Distkeys and sortkeys are only used by
        # Redshift and should not be passed when generating a create statement for
        # Postgres.

        if tbl.num_rows == 0:
            raise ValueError("Table is empty. Must have 1 or more rows.")

        # Validate and rename column names if needed
        tbl.table = petl.setheader(tbl.table, self.column_name_validate(tbl.columns))

        mapping = self.generate_data_types(tbl)

        if padding:
            mapping["longest"] = self.vc_padding(mapping, padding)
        elif not strict_length:
            mapping["longest"] = self.vc_step(mapping)

        if varchar_max:
            mapping["longest"] = self.vc_max(mapping, varchar_max)

        if varchar_truncate:
            mapping["longest"] = self.vc_trunc(mapping)

        mapping["longest"] = self.vc_validate(mapping)

        # Add any provided column type overrides
        if columntypes:
            for i in range(len(mapping["headers"])):
                col = mapping["headers"][i]
                if columntypes.get(col):
                    mapping["type_list"][i] = columntypes[col]

        # Enclose in quotes
        mapping["headers"] = [f'"{h}"' for h in mapping["headers"]]

        return self.create_sql(table_name, mapping, distkey=distkey, sortkey=sortkey)

    # This is for backwards compatability
    def data_type(self, val, current_type):
        return self.detect_data_type(val, current_type)

    # This is for backwards compatability
    def is_valid_integer(self, val):
        return self.is_valid_sql_num(val)

    def generate_data_types(self, table):
        # Generate column data types

        longest, type_list = [], []

        cont = petl.records(table.table)

        # Populate empty values for the columns
        for col in table.columns:
            longest.append(0)
            type_list.append("")

        for row in cont:
            for i in range(len(row)):
                # NA is the csv null value
                if type_list[i] == "varchar" or row[i] in ["NA", ""]:
                    pass
                else:
                    var_type = self.data_type(row[i], type_list[i])
                    type_list[i] = var_type

                # Calculate width
                width = len(str(row[i]).encode("utf-8"))
                if width > longest[i]:
                    longest[i] = width

        # In L140 'NA' and '' will be skipped
        # If the entire column is either one of those (or a mix of the two)
        # the type will be empty.
        # Fill with a default varchar
        type_list = [typ or "varchar" for typ in type_list]

        return {"longest": longest, "headers": table.columns, "type_list": type_list}

    def vc_padding(self, mapping, padding):
        # Pad the width of a varchar column

        return [int(c + (c * padding)) for c in mapping["longest"]]

    def vc_step(self, mapping):
        return [self.round_longest(c) for c in mapping["longest"]]

    def vc_max(self, mapping, columns):
        # Set the varchar width of a column to the maximum

        for c in columns:

            try:
                idx = mapping["headers"].index(c)
                mapping["longest"][idx] = self.VARCHAR_MAX

            except KeyError as error:
                logger.error("Could not find column name provided.")
                raise error

        return mapping["longest"]

    def vc_trunc(self, mapping):

        return [
            self.VARCHAR_MAX if c > self.VARCHAR_MAX else c for c in mapping["longest"]
        ]

    def vc_validate(self, mapping):

        return [1 if c == 0 else c for c in mapping["longest"]]

    def create_sql(self, table_name, mapping, distkey=None, sortkey=None):
        # Generate the sql to create the table

        statement = f"create table {table_name} ("

        for i in range(len(mapping["headers"])):
            if mapping["type_list"][i] == "varchar":
                statement = (statement + "\n  {} varchar({}),").format(
                    str(mapping["headers"][i]).lower(), str(mapping["longest"][i])
                )
            else:
                statement = (statement + "\n  " + "{} {}" + ",").format(
                    str(mapping["headers"][i]).lower(), mapping["type_list"][i]
                )

        statement = statement[:-1] + ") "

        if distkey:
            statement += f"\ndistkey({distkey}) "

        if sortkey:
            statement += f"\nsortkey({sortkey})"

        statement += ";"

        return statement

    # This is for backwards compatability
    def column_name_validate(self, columns):
        return self.format_columns(columns, col_prefix="col_")

    @staticmethod
    def round_longest(longest):
        # Find the value that will work best to fit our longest column value
        for step in consts.VARCHAR_STEPS:
            # Make sure we have padding
            if longest < step / 2:
                return step

        return consts.VARCHAR_MAX
