import json
from typing import Optional

from edtf_validate.valid_edtf import (
    conformsLevel0,
    conformsLevel1,
    conformsLevel2,
    is_valid,
)

from great_expectations.core.expectation_configuration import ExpectationConfiguration
from great_expectations.execution_engine import (
    PandasExecutionEngine,
    SparkDFExecutionEngine,
)
from great_expectations.expectations.expectation import (
    ColumnMapExpectation,
    ExpectationConfiguration,
)
from great_expectations.expectations.metrics.import_manager import F, sparktypes
from great_expectations.expectations.metrics.map_metric import (
    ColumnMapMetricProvider,
    column_condition_partial,
)
from great_expectations.expectations.util import render_evaluation_parameter_string
from great_expectations.render.renderer.renderer import renderer
from great_expectations.render.types import RenderedStringTemplateContent
from great_expectations.render.util import (
    num_to_str,
    parse_row_condition_string_pandas_engine,
    substitute_none_for_missing,
)


def complies_to_level(value, level=None):
    if level == 0:
        return conformsLevel0(value)
    elif level == 1:
        return conformsLevel1(value)
    elif level == 2:
        return conformsLevel2(value)

    return is_valid(value)


class ColumnValuesEdtfParseable(ColumnMapMetricProvider):
    condition_metric_name = "column_values.edtf_parseable"

    @column_condition_partial(engine=PandasExecutionEngine)
    def _pandas(cls, column, level=None, **kwargs):
        def is_parseable(val):
            try:
                if type(val) != str:
                    raise TypeError(
                        "Values passed to expect_column_values_to_be_edtf_parseable must be of type string.\nIf you want to validate a column of dates or timestamps, please call the expectation before converting from string format."
                    )

                return complies_to_level(val, level)

            except (ValueError, OverflowError):
                return False

        if level is not None and type(level) != int:
            raise TypeError("level must be of type int.")

        return column.map(is_parseable)

    @column_condition_partial(engine=SparkDFExecutionEngine)
    def _spark(cls, column, level=None, **kwargs):
        def is_parseable(val):
            try:
                if type(val) != str:
                    raise TypeError(
                        "Values passed to expect_column_values_to_be_edtf_parseable must be of type string.\nIf you want to validate a column of dates or timestamps, please call the expectation before converting from string format."
                    )

                return complies_to_level(val, level)

            except (ValueError, OverflowError):
                return False

        if level is not None and type(level) != int:
            raise TypeError("level must be of type int.")

        is_parseable_udf = F.udf(is_parseable, sparktypes.BooleanType())
        return is_parseable_udf(column)


class ExpectColumnValuesToBeEdtfParseable(ColumnMapExpectation):
    """Expect column entries to be parsable using the [Extended Date/Time Format (EDTF) specification](https://www.loc.gov/standards/datetime/).

    expect_column_values_to_be_edtf_parseable is a \
    :func:`column_map_expectation <great_expectations.execution_engine.execution_engine.MetaExecutionEngine
    .column_map_expectation>`.

    Args:
        column (str): \
            The column name.
        level (int or None): \
            The EDTF level to comply to.

    Keyword Args:
        mostly (None or a float between 0 and 1): \
            Return `"success": True` if at least mostly fraction of values match the expectation. \
            For more detail, see :ref:`mostly`.

    Other Parameters:
        result_format (str or None): \
            Which output mode to use: `BOOLEAN_ONLY`, `BASIC`, `COMPLETE`, or `SUMMARY`.
            For more detail, see :ref:`result_format <result_format>`.
        include_config (boolean): \
            If True, then include the expectation config as part of the result object. \
            For more detail, see :ref:`include_config`.
        catch_exceptions (boolean or None): \
            If True, then catch exceptions and include them as part of the result object. \
            For more detail, see :ref:`catch_exceptions`.
        meta (dict or None): \
            A JSON-serializable dictionary (nesting allowed) that will be included in the output without \
            modification. For more detail, see :ref:`meta`.

    Returns:
        An ExpectationSuiteValidationResult

        Exact fields vary depending on the values passed to :ref:`result_format <result_format>` and
        :ref:`include_config`, :ref:`catch_exceptions`, and :ref:`meta`.

    """

    # These examples will be shown in the public gallery, and also executed as unit tests for your Expectation
    examples = [
        {
            "data": {
                "all_edtf_l0": [
                    "1964/2008",
                    "2004-06/2006-08",
                    "2004-02-01/2005-02-08",
                    "2004-02-01/2005-02",
                    "2004-02-01/2005",
                    "2005/2006-02",
                    "0000/0000",
                    "0000-02/1111",
                    "0000-01/0000-01-03",
                    "0000-01-13/0000-01-23",
                    "1111-01-01/1111",
                    "0000-01/0000",
                ],
            },
            "tests": [
                {
                    "title": "positive_level0_test_exact",
                    "include_in_gallery": True,
                    "exact_match_out": False,
                    "in": {"column": "all_edtf_l0", "level": 0},
                    "out": {
                        "success": True,
                        "unexpected_count": 0,
                    },
                },
            ],
        },
        {
            "data": {
                "all_edtf_l1": [
                    "-1000/-0999",
                    "-2004-02-01/2005",
                    "-1980-11-01/1989-11-30",
                    "1923-21/1924",
                    "2019-12/2020%",
                    "1984~/2004-06",
                    "1984/2004-06~",
                    "1984~/2004~",
                    "-1984?/2004%",
                    "1984?/2004-06~",
                    "1984-06?/2004-08?",
                    "1984-06-02?/2004-08-08~",
                    "2004-06~/2004-06-11%",
                    "1984-06-02?/",
                    "2003/2004-06-11%",
                    "1952-23~/1953",
                    "-2004-06-01/",
                    "1985-04-12/",
                    "1985-04/",
                    "1985/",
                    "/1985-04-12",
                    "/1985-04",
                    "/1985",
                    "2003-22/2004-22",
                    "2003-22/2003-22",
                    "2003-22/2003-23",
                    "1985-04-12/..",
                    "1985-04/..",
                    "1985/..",
                    "../1985-04-12",
                    "../1985-04",
                    "../1985",
                    "/..",
                    "../",
                    "../..",
                    "-1985-04-12/..",
                    "-1985-04/..",
                    "-1985/",
                ]
            },
            "tests": [
                {
                    "title": "positive_level1_test_exact",
                    "include_in_gallery": True,
                    "exact_match_out": False,
                    "in": {"column": "all_edtf_l1", "level": 1},
                    "out": {
                        "success": True,
                        "unexpected_count": 0,
                    },
                },
            ],
        },
        {
            "data": {
                "all_edtf_l2": [
                    "2004-06-~01/2004-06-~20",
                    "-2004-06-?01/2006-06-~20",
                    "-2005-06-%01/2006-06-~20",
                    "2019-12/%2020",
                    "1984?-06/2004-08?",
                    "-1984-?06-02/2004-08-08~",
                    "1984-?06-02/2004-06-11%",
                    "2019-~12/2020",
                    "2003-06-11%/2004-%06",
                    "2004-06~/2004-06-%11",
                    "1984?/2004~-06",
                    "?2004-06~-10/2004-06-%11",
                    "2004-06-XX/2004-07-03",
                    "2003-06-25/2004-X1-03",
                    "20X3-06-25/2004-X1-03",
                    "XXXX-12-21/1890-09-2X",
                    "1984-11-2X/1999-01-01",
                    "1984-11-12/1984-11-XX",
                    "198X-11-XX/198X-11-30",
                    "2000-12-XX/2012",
                    "-2000-12-XX/2012",
                    "2000-XX/2012",
                    "2000-XX-XX/2012",
                    "2000-XX-XX/2012",
                    "-2000-XX-10/2012",
                    "2000/2000-XX-XX",
                    "198X/199X",
                    "198X/1999",
                    "1987/199X",
                    "1919-XX-02/1919-XX-01",
                    "1919-0X-02/1919-01-03",
                    "1865-X2-02/1865-03-01",
                    "1930-X0-10/1930-10-30",
                    "1981-1X-10/1981-11-09",
                    "1919-12-02/1919-XX-04",
                    "1919-11-02/1919-1X-01",
                    "1919-09-02/1919-X0-01",
                    "1919-08-02/1919-0X-01",
                    "1919-10-02/1919-X1-01",
                    "1919-04-01/1919-X4-02",
                    "1602-10-0X/1602-10-02",
                    "2018-05-X0/2018-05-11",
                    "-2018-05-X0/2018-05-11",
                    "1200-01-X4/1200-01-08",
                    "1919-07-30/1919-07-3X",
                    "1908-05-02/1908-05-0X",
                    "0501-11-18/0501-11-1X",
                    "1112-08-22/1112-08-2X",
                    "2015-02-27/2015-02-X8",
                    "2016-02-28/2016-02-X9",
                    "1984-06-?02/2004-06-11%",
                ]
            },
            "tests": [
                {
                    "title": "positive_level2_test_exact",
                    "include_in_gallery": True,
                    "exact_match_out": False,
                    "in": {"column": "all_edtf_l2", "level": 2},
                    "out": {
                        "success": True,
                        "unexpected_count": 0,
                    },
                },
            ],
        },
        {
            "data": {
                "invalid_edtf_dates": [
                    "1863- 03-29",
                    " 1863-03-29",
                    "1863-03 -29",
                    "1863-03- 29",
                    "1863-03-29 ",
                    "18 63-03-29",
                    "1863-0 3-29",
                    "1960-06-31",
                    "20067890%",
                    "Y2006",
                    "-0000",
                    "Y20067890-14-10%",
                    "20067890%",
                    "+2006%",
                    "NONE/",
                    "2000/12-12",
                    "2012-10-10T1:10:10",
                    "2012-10-10T10:1:10",
                    "2005-07-25T10:10:10Z/2006-01-01T10:10:10Z",
                    "[1 760-01, 1760-02, 1760-12..]",
                    "[1667,1668, 1670..1672]",
                    "[..176 0-12-03]",
                    "{-1667,1668, 1670..1672}",
                    "{-1667,1 668-10,1670..1672}",
                    "2001-21^southernHemisphere",
                    "",
                ]
            },
            "tests": [
                {
                    "title": "negative_test_exact",
                    "include_in_gallery": True,
                    "exact_match_out": False,
                    "in": {"column": "invalid_edtf_dates"},
                    "out": {
                        "success": False,
                        "unexpected_count": 25,
                    },
                },
            ],
        },
        {
            "data": {
                "mostly_edtf": [
                    "1979-08",  # ISO8601 Date
                    "2004-01-01T10:10:10+05:00",  # ISO8601 Datetime
                    "1979-08-28/1979-09-25",  # Interval (start/end)
                    "1979-08~",  # Uncertain/Approximate dates
                    "1979-08-XX",  # Unspecified dates
                    "1984-06-02?/2004-08-08~",  # Extended intervals
                    "y-12000",  # Years exceeding four digits
                    "asdwefefef",
                    "Octobre 12",
                    None,
                    None,
                ],
            },
            "tests": [
                {
                    "title": "positive_test_with_mostly",
                    "include_in_gallery": True,
                    "exact_match_out": False,
                    "in": {"column": "mostly_edtf", "mostly": 0.6},
                    "out": {
                        "success": True,
                        "unexpected_index_list": [6, 7],
                        "unexpected_list": [2, -1],
                        "unexpected_count": 2,
                    },
                }
            ],
        },
    ]

    # This dictionary contains metadata for display in the public gallery
    library_metadata = {
        "maturity": "experimental",  # "experimental", "beta", or "production"
        "tags": ["edtf", "datetime", "glam"],
        "contributors": ["@mielvds"],
        "package": "experimental_expectations",
        "requirements": ["edtf_validate"],
    }

    map_metric = "column_values.edtf_parsable"
    success_keys = ("mostly",)

    default_kwarg_values = {
        "row_condition": None,
        "condition_parser": None,  # we expect this to be explicitly set whenever a row_condition is passed
        "mostly": 1,
        "result_format": "BASIC",
        "include_config": True,
        "catch_exceptions": True,
    }

    def validate_configuration(self, configuration: Optional[ExpectationConfiguration]):
        super().validate_configuration(configuration)
        return True

    @classmethod
    @renderer(renderer_type="renderer.prescriptive")
    @render_evaluation_parameter_string
    def _prescriptive_renderer(
        cls,
        configuration=None,
        result=None,
        language=None,
        runtime_configuration=None,
        **kwargs
    ):
        runtime_configuration = runtime_configuration or {}
        include_column_name = runtime_configuration.get("include_column_name", True)
        include_column_name = (
            include_column_name if include_column_name is not None else True
        )
        styling = runtime_configuration.get("styling")
        params = substitute_none_for_missing(
            configuration.kwargs,
            ["column", "mostly", "row_condition", "condition_parser"],
        )

        template_str = "values must be parseable by edtf"

        if params["mostly"] is not None:
            params["mostly_pct"] = num_to_str(
                params["mostly"] * 100, precision=15, no_scientific=True
            )
            # params["mostly_pct"] = "{:.14f}".format(params["mostly"]*100).rstrip("0").rstrip(".")
            template_str += ", at least $mostly_pct % of the time."
        else:
            template_str += "."

        if include_column_name:
            template_str = "$column " + template_str

        if params["row_condition"] is not None:
            (
                conditional_template_str,
                conditional_params,
            ) = parse_row_condition_string_pandas_engine(params["row_condition"])
            template_str = conditional_template_str + ", then " + template_str
            params.update(conditional_params)

        return [
            RenderedStringTemplateContent(
                **{
                    "content_block_type": "string_template",
                    "string_template": {
                        "template": template_str,
                        "params": params,
                        "styling": styling,
                    },
                }
            )
        ]


if __name__ == "__main__":
    diagnostics_report = ExpectColumnValuesToBeEdtfParseable().run_diagnostics()
    print(json.dumps(diagnostics_report, indent=2))
