import re
from datetime import datetime

from django.apps import apps
from django.core.exceptions import ValidationError
from api.translation import __

#work like laravel validate
class BaseValidator:
    def __init__(self, data, rules, attributes=None):
        self.data = data
        self.rules = rules
        self.attributes = attributes or {}
        self.errors = {}
        self.first_message = None

    def validate(self):
        for field, rule_str in self.rules.items():
            value = self.data.get(field)
            rules = rule_str.split("|")

            try:
                for rule in rules:
                    # Handle rule with params (gte:0)
                    path_from = None
                    if ":" in rule:
                        rule_name, param = rule.split(":", 1)
                        if "?" in param:
                            param, path_from = param.split("?", 1)
                    else:
                        rule_name, param = rule, None

                    method_name = f"rule_{rule_name}"
                    if hasattr(self, method_name):
                        if path_from is None:
                            getattr(self, method_name)(field, value, param)
                        else:
                            getattr(self, method_name)(field, value, param, path_from)

                    # when hit first error, direct return to view
                    # if self.errors:
                    #     return self.errors

            except StopIteration:
                # `nullable` triggered, skip remaining rules for this field
                continue

        if self.errors:
            return {
                'error': self.first_message,
                'errors': self.errors,
            }

        return self.errors

    def fails(self):
        return bool(self.errors)

    def rule_nullable(self, field, value, param):
        if value in (None, ""):
            raise StopIteration  # Stop checking this field's rules

    def rule_required(self, field, value, param):
        if value in (None, "", [], {}, ()):
            self.add_error(field, __("validation.required", field=self.get_attr(field)))

    def rule_integer(self, field, value, param):
        if isinstance(value, bool):
            self.add_error(field, __("validation.integer", field=self.get_attr(field)))
            return

        if isinstance(value, int):
            return

        if isinstance(value, str) and re.fullmatch(r"-?\d+", value.strip()):
            return

        self.add_error(field, __("validation.integer", field=self.get_attr(field)))

    def rule_numeric(self, field, value, param):
        try:
            float(value)
        except (TypeError, ValueError):
            self.add_error(field, __("validation.numeric", field=self.get_attr(field)))

    def rule_string(self, field, value, param):
        if not isinstance(value, str):
            self.add_error(field, __("validation.string", field=self.get_attr(field)))

    def rule_date(self, field, value, param):
        try:
            # Try to parse as a date (YYYY-MM-DD)
            datetime.strptime(value, "%Y-%m-%d")
        except Exception:
            self.add_error(field, __("validation.date", field=self.get_attr(field)))

    def rule_datetime(self, field, value, param):
        try:
            if isinstance(value, datetime):
                return

            if isinstance(value, str):
                datetime.fromisoformat(value.replace("Z", "+00:00"))
                return
        except Exception:
            pass

        self.add_error(field, __("validation.datetime", field=self.get_attr(field)))

    def rule_time(self, field, value, param):
        try:
            # Try to parse as a time (HH:MM or HH:MM:SS)
            datetime.strptime(value, "%H:%M:%S")
        except (ValueError, TypeError):
            try:
                datetime.strptime(value, "%H:%M")  # allow shorter format
            except Exception:
                self.add_error(field, __("validation.time", field=self.get_attr(field)))

    def rule_char_count(self, field, value, param):
        try:
            expected = int(param)
            if len(value) != expected:
                self.add_error(field,__("validation.char_count", field=self.get_attr(field), value=expected))
        except Exception:
            self.add_error(field, __("validation.char_count", field=self.get_attr(field), value=expected))

    def rule_array(self, field, value, param):
        if not isinstance(value, list):
            self.add_error(field, __("validation.array", field=self.get_attr(field)))

    def rule_min(self, field, value, param):
        try:
            if isinstance(value, (int, float)):
                if value < float(param):
                    self.add_error(field, __("validation.min_numeric", field=self.get_attr(field), min=param))
            elif isinstance(value, str):
                if len(value) < int(param):
                    self.add_error(field, __("validation.min_string", field=self.get_attr(field), min=param))
        except Exception:
            self.add_error(field, __("validation.min_generic", field=self.get_attr(field), min=param))

    def rule_max(self, field, value, param):
        try:
            if isinstance(value, (int, float)):
                if value > float(param):
                    self.add_error(field, __("validation.max_numeric", field=self.get_attr(field), max=param))
            elif isinstance(value, str):
                if len(value) > int(param):
                    self.add_error(field, __("validation.max_string", field=self.get_attr(field), max=param))
        except Exception:
            self.add_error(field, __("validation.max_generic", field=self.get_attr(field), max=param))

    def rule_gte(self, field, value, param):
        try:
            if float(value) < float(param):
                self.add_error(field, __("validation.gte", field=self.get_attr(field), value=param))
        except Exception:
            self.add_error(field, __("validation.numeric", field=self.get_attr(field)))

    def rule_lte(self, field, value, param):
        try:
            if float(value) > float(param):
                self.add_error(field, __("validation.lte", field=self.get_attr(field), value=param))
        except Exception:
            self.add_error(field, __("validation.numeric", field=self.get_attr(field)))

    def rule_email(self, field, value, param):
        if not value or not re.match(r"[^@]+@[^@]+\.[^@]+", str(value)):
            self.add_error(field, __("validation.email", field=self.get_attr(field)))

    def rule_in(self, field, value, param, path_from=None):
        # if param is empty, always throw fail error
        allowed = param.split(",") if param else []
        if str(value) not in allowed:
            convert_string = []
            if path_from is not None:
                convert_string = [self.get_attr(path_from+"."+a) for a in allowed if len(allowed) > 0]
            self.add_error(field, __("validation.in", field=self.get_attr(field), values=", ".join(convert_string if convert_string != [] else allowed)))

    def rule_not_in(self, field, value, param, path_from=None):
        disallowed = param.split(",")
        if str(value) in disallowed:
            convert_string = []
            if path_from is not None:
                convert_string = [self.get_attr(path_from+"."+a) for a in disallowed if len(disallowed) > 0]
            self.add_error(field, __("validation.not_in", field=self.get_attr(field), values=", ".join(convert_string if convert_string != [] else disallowed)))

    def rule_exists(self, field, value, param):
        model_path, field_name = param.split(",")

        model = apps.get_model(model_path)
        if isinstance(value, (list, tuple)):
            existing = model.objects.filter(**{f"{field_name}__in": value}).values_list(field_name, flat=True)
            not_found = [v for v in value if v not in existing]
            if not_found:
                # self.add_error(field, __("validation.exists", field=self.get_attr(field), values=not_found))
                self.add_error(field, __("validation.invalid", field=self.get_attr(field)))

        else:
            exists = model.objects.filter(**{field_name: value}).exists()
            if not exists:
                # self.add_error(field, __("validation.exists", field=self.get_attr(field), values=value))
                self.add_error(field, __("validation.invalid", field=self.get_attr(field)))

    def rule_not_exists(self, field, value, param):
        model_path, field_name = param.split(",")
        model = apps.get_model(model_path)

        if isinstance(value, (list, tuple)):
            # Get all existing records that match any of the values
            existing = set(model.objects.filter(**{f"{field_name}__in": value}).values_list(field_name, flat=True))
            found = [v for v in value if v in existing]
            if found:
                self.add_error(field, __("validation.not_exists", field=self.get_attr(field), values=found))
        else:
            exists = model.objects.filter(**{field_name: value}).exists()
            if exists:
                self.add_error(field, __("validation.not_exists", field=self.get_attr(field), values=value))

    def rule_regex(self, field, value, pattern):
        # if None will get error, so when is None, just skip
       if value is not None and not re.fullmatch(pattern, value):
             self.add_error(
                field,
                __("validation.invalid_format")
            )

    def get_attr(self, field):
        return self.attributes.get(field, __("attr." + field))

    def add_error(self, field, message):
        if self.first_message is None:
            self.first_message = message
        self.errors.setdefault(field, []).append(message)
        # self.errors.append(message)