import re

from . import consts
from .model import Format, PackageFilter, Step


class ValidationError(BaseException):
    def __init__(self, field_name: str, message: str):
        self.field_name = field_name
        self.message = message


def _validate_required(field_name: str, value) -> list[ValidationError]:
    if value is None:
        return [ValidationError(
            field_name=field_name,
            message='Missing but it is required',
        )]
    return []


def _validate_non_empty(field_name: str, value) -> list[ValidationError]:
    if value is not None and len(value.strip()) == 0:
        return [ValidationError(
            field_name=field_name,
            message='Cannot be empty or only-whitespace',
        )]
    return []


def _validate_content_type(field_name: str, value) -> list[ValidationError]:
    if value is not None and re.match(consts.REGEX_MIME_TYPE, value) is None:
        return [ValidationError(
            field_name=field_name,
            message='Content type should be valid IANA media type',
        )]
    return []


def _validate_extension(field_name: str, value) -> list[ValidationError]:
    if value is not None and re.match(consts.REGEX_ORGANIZATION_ID, value) is None:
        return [ValidationError(
            field_name=field_name,
            message='File extension should contain only letters, numbers, '
                    'and periods (inside-only)',
        )]
    return []


def _validate_organization_id(field_name: str, value) -> list[ValidationError]:
    if value is not None and re.match(consts.REGEX_ORGANIZATION_ID, value) is None:
        return [ValidationError(
            field_name=field_name,
            message='Organization ID may contain only letters, numbers, '
                    'periods, dashes, and underscores',
        )]
    return []


def _validate_template_id(field_name: str, value) -> list[ValidationError]:
    if value is not None and re.match(consts.REGEX_TEMPLATE_ID, value) is None:
        return [ValidationError(
            field_name=field_name,
            message='Template ID may contain only letters, numbers, '
                    'periods, dashes, and underscores',
        )]
    return []


def _validate_km_id(field_name: str, value) -> list[ValidationError]:
    if value is not None and re.match(consts.REGEX_KM_ID, value) is None:
        return [ValidationError(
            field_name=field_name,
            message='KM ID may contain only letters, numbers, '
                    'periods, dashes, and underscores',
        )]
    return []


def _validate_version(field_name: str, value) -> list[ValidationError]:
    if value is not None and re.match(consts.REGEX_SEMVER, value) is None:
        return [ValidationError(
            field_name=field_name,
            message='Version must be in semver format <NUM>.<NUM>.<NUM>',
        )]
    return []


def _validate_natural(field_name: str, value) -> list[ValidationError]:
    if value is not None and (not isinstance(value, int) or value < 1):
        return [ValidationError(
            field_name=field_name,
            message='It must be positive integer',
        )]
    return []


def _validate_metamodel_version(field_name: str, value) -> list[ValidationError]:
    if isinstance(value, int) and value > 0:
        return []
    if isinstance(value, str) and '.' in value:
        parts = value.split('.')
        if len(parts) != 2:
            return [ValidationError(
                field_name=field_name,
                message='It must be in format <MAJOR>.<MINOR>',
            )]
        try:
            major = int(parts[0])
            minor = int(parts[1])
            if major < 1 or minor < 0:
                return [ValidationError(
                    field_name=field_name,
                    message='It must be in format <MAJOR>.<MINOR> '
                            'with MAJOR >= 1 and MINOR >= 0',
                )]
            return []
        except ValueError:
            return [ValidationError(
                field_name=field_name,
                message='It must be in format <MAJOR>.<MINOR> '
                        'with MAJOR >= 1 and MINOR >= 0',
            )]
    return [ValidationError(
        field_name=field_name,
        message='It must be a positive integer or in format <MAJOR>.<MINOR>',
    )]


def _validate_package_id(field_name: str, value: str) -> list[ValidationError]:
    res = []
    if value is None:
        return res
    if not isinstance(value, str):
        return [ValidationError(
            field_name=field_name,
            message='Package ID is not a string',
        )]
    parts = value.split(':')
    if len(parts) != 3:
        res.append(ValidationError(
            field_name=field_name,
            message=f'Package ID is not valid (only {len(parts)} parts)',
        ))
    if re.match(consts.REGEX_ORGANIZATION_ID, parts[0]) is None:
        res.append(ValidationError(
            field_name=field_name,
            message='Package ID contains invalid organization id',
        ))
    if re.match(consts.REGEX_KM_ID, parts[1]) is None:
        res.append(ValidationError(
            field_name=field_name,
            message='Package ID contains invalid KM id',
        ))
    if re.match(consts.REGEX_SEMVER, parts[2]) is None:
        res.append(ValidationError(
            field_name=field_name,
            message='Package ID contains invalid version',
        ))
    return res


def _validate_jinja_options(field_name: str, value: dict[str, str]) -> list[ValidationError]:
    res = []
    if value is None:
        return res
    for k in ('template', 'content-type', 'extension'):
        if k not in value:
            res.append(ValidationError(
                field_name=field_name,
                message='Jinja option cannot be left out',
            ))
        elif value[k] is None or not isinstance(value[k], str) or len(value[k]) == 0:
            res.append(ValidationError(
                field_name=field_name,
                message='Jinja option cannot be empty',
            ))
    if 'content-type' in value:
        res.extend(_validate_content_type(
            field_name=f'{field_name}.content-type',
            value=value['content-type'],
        ))
    return res


class GenericValidator:

    def __init__(self, rules):
        self.rules = rules

    def validate_field(self, entity, field_name: str):
        for validator in self.rules.get(field_name, []):
            err = validator(field_name, getattr(entity, field_name))
            if len(err) != 0:
                raise err[0]

    def validate(self, entity, field_name_prefix: str = ''):
        for field_name, validators in self.rules.items():
            if field_name.startswith('__'):
                continue
            for validator in validators:
                err = validator(field_name_prefix + field_name, getattr(entity, field_name))
                if len(err) != 0:
                    raise err[0]
        if '__all' in self.rules:
            err = self.rules['__all'](field_name_prefix, entity)
            if len(err) != 0:
                raise err[0]

    def collect_errors(self, entity, field_name_prefix: str = '') -> list[ValidationError]:
        result = []
        for field_name, validators in self.rules.items():
            if field_name.startswith('__'):
                continue
            for validator in validators:
                result.extend(validator(
                    field_name=f'{field_name_prefix}{field_name}',
                    value=getattr(entity, field_name),
                ))
        if '__all' in self.rules:
            result.extend(self.rules['__all'](field_name_prefix, entity))
        return result


PackageFilterValidator = GenericValidator({
    'organization_id': [_validate_organization_id],
    'km_id': [_validate_km_id],
    'min_version': [_validate_version],
    'max_version': [_validate_version],
})


def _validate_package_filters(field_name: str, value: list[PackageFilter]) -> list[ValidationError]:
    res = []
    for v in value:
        res.extend(PackageFilterValidator.collect_errors(v, field_name_prefix=f'{field_name}.'))
    return res


def _validate_step(field_name_prefix: str, value: Step) -> list[ValidationError]:
    if value.name == 'jinja':
        return _validate_jinja_options(f'{field_name_prefix}options', value.options)
    return []


StepValidator = GenericValidator({
    'name': [_validate_non_empty],
    'options': [],
    '__all': _validate_step,
})


def _validate_steps(field_name: str, value: list[Step]) -> list[ValidationError]:
    res = []
    for v in value:
        res.extend(StepValidator.collect_errors(v, field_name_prefix=f'{field_name}.'))
    return res


FormatValidator = GenericValidator({
    'uuid': [_validate_required, _validate_non_empty],
    'name': [_validate_required, _validate_non_empty],
    'icon': [_validate_required, _validate_non_empty],
    'steps': [_validate_required, _validate_steps],
})


def _validate_formats(field_name: str, value: list[Format]) -> list[ValidationError]:
    res = []
    uuids = set()
    for v in value:
        if v.uuid in uuids:
            res.append(ValidationError(field_name, f'Duplicate format UUID {v.uuid}'))
        uuids.add(v.uuid)
        res.extend(FormatValidator.collect_errors(v, field_name_prefix=f'{field_name}.'))
    return res


TemplateValidator = GenericValidator({
    'template_id': [_validate_required, _validate_template_id],
    'organization_id': [_validate_required, _validate_organization_id],
    'version': [_validate_required, _validate_version],
    'name': [_validate_required, _validate_non_empty],
    'description': [_validate_required, _validate_non_empty],
    'readme': [_validate_required, _validate_non_empty],
    'license': [_validate_required, _validate_non_empty],
    'metamodel_version': [_validate_metamodel_version, _validate_required],
    'allowed_packages': [_validate_package_filters],
    'formats': [_validate_required, _validate_formats],
})
