import re
from datetime import datetime

from .object import Object
from .utils import df_military_time, day_of_week
from .enum import FileCriteria, BooleanFunction, Application, ScheduleType


class PolicyRule:

    def __init__(self, assignment, criteria):
        self.assignment = assignment
        self.criteria = criteria


class PolicyRuleConverter:

    @staticmethod
    def convert(rule, classname, property_name, assignment=None):
        param = Object()
        param._classname = classname  # pylint: disable=protected-access
        setattr(param, property_name, assignment if assignment else rule.assignment)
        param.filterRule = rule.criteria
        return param


class Operator(Object):

    def __init__(self, right):
        self._classname = self.__class__.__name__  # pylint: disable=protected-access
        self.right = right


class IsOperator(Operator):
    pass


class BeginsWithOperator(Operator):
    pass


class EndsWithOperator(Operator):
    pass


class ContainsOperator(Operator):
    pass


class IsOneOfOperator(Operator):
    pass


class LessThanOperator(Operator):
    pass


class MoreThanOperator(Operator):
    pass


class BeforeOperator(Operator):
    pass


class AfterOperator(Operator):
    pass


class AdvancedFilterRule(Object):

    def __init__(self, classname, field, operator):
        self._classname = self.__class__.__name__  # pylint: disable=protected-access
        self.className = classname
        self.fieldName = field
        self.operator = operator


class CriteriaBuilder:

    def __init__(self, criteria_type, criteria_field):
        self.criteria_type = criteria_type
        self.criteria_field = criteria_field
        self.operator = None

    def build(self):
        return AdvancedFilterRule(self.criteria_type, self.criteria_field, self.operator)


class ListCriteriaBuilder(CriteriaBuilder):

    def include(self, values):
        self.operator = IsOneOfOperator(values)
        return self


class IntegerCriteriaBuilder(CriteriaBuilder):

    def less_than(self, value):
        self.operator = LessThanOperator(value)
        return self

    def more_than(self, value):
        self.operator = MoreThanOperator(value)
        return self


class DateTimeCriteriaBuilder(CriteriaBuilder):

    def before(self, value):
        self.operator = BeforeOperator(value)
        return self

    def after(self, value):
        self.operator = AfterOperator(value)
        return self


class StringCriteriaBuilder(CriteriaBuilder):

    def equals(self, value):
        self.operator = IsOperator(value)
        return self

    def startswith(self, value):
        self.operator = BeginsWithOperator(value)
        return self

    def endswith(self, value):
        self.operator = EndsWithOperator(value)
        return self

    def contains(self, value):
        self.operator = ContainsOperator(value)
        return self

    def isoneof(self, values):
        self.operator = IsOneOfOperator(values)
        return self


class ThrottlingRule:
    """
    Throttling Rule

    :ivar int upload: Throttling rate upstream (Kilobits)
    :ivar int download: Throttling rate downstream (Kilobits)
    :ivar str start: Start time
    :ivar str end: End time
    :ivar list[str] days: Days
    """

    def __init__(self):
        self.upload = None
        self.download = None
        self.schedule = None

    def to_server_object(self):
        param = Object()
        param._classname = 'SyncThrottlingSettings'  # pylint: disable=protected-access
        param.inKbitsPerSecond = self.download
        param.outKbitsPerSecond = self.upload
        param.schedule = self.schedule
        return param

    @staticmethod
    def from_server_object(param):
        r = ThrottlingRule()
        r.download = param.inKbitsPerSecond
        r.upload = param.outKbitsPerSecond
        r.schedule = param.schedule
        return r

    def __str__(self):
        return str(dict(upload_kbps=self.upload, download_kbps=self.download,
                        start=self.schedule.start, end=self.schedule.end, days=[day_of_week(day) for day in self.schedule.days]))


class ThrottlingRuleBuilder:
    """
    Bandwidth Throttling Rule Builder
    """

    def __init__(self):
        self.param = ThrottlingRule()

    def upload(self, kbps):
        """
        Throttle bandwidth upstream

        :param int kbps: Kilobits per second
        """
        self.param.upload = kbps
        return self

    def download(self, kbps):
        """
        Throttle bandwidth downstream

        :param int kbps: Kilobits per second
        """
        self.param.download = kbps
        return self

    def schedule(self, schedule):
        """
        Set the throttling rule schedule

        :param cterasdk.common.types.TimeRange schedule: Schedule
        """
        self.param.schedule = schedule
        return self

    def build(self):
        """
        Build the throttling rule
        """
        errors = [k for k, v in self.param.__dict__.items() if v is None]
        if errors:
            raise ValueError(f'No value for required field: {errors}')
        return self.param


class FileFilterBuilder:

    Type = 'File'

    @staticmethod
    def extensions():
        """Filter files by extension"""
        return ListCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Type)

    @staticmethod
    def names():
        """Filter files by names"""
        return ListCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Name)

    @staticmethod
    def name():
        """Filter files by name pattern"""
        return StringCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Name)

    @staticmethod
    def paths():
        """Filter files by path"""
        return ListCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Path)

    @staticmethod
    def path():
        """Filter files by path pattern"""
        return StringCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Path)

    @staticmethod
    def size():
        """Filter files by size"""
        return IntegerCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Size)

    @staticmethod
    def last_modified():
        """Filter files by last modification date"""
        return DateTimeCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Modified)


class DirectoryEntryFactory:

    @staticmethod
    def root(included):
        return DirEntry('root', included=included)


class FileEntry(Object):

    def __init__(self, name, display_name=None, included=None):
        self._classname = self.__class__.__name__  # pylint: disable=protected-access
        self.name = name
        self.displayName = display_name
        self.isIncluded = included


class DirEntry(FileEntry):

    def __init__(self, name, display_name=None, included=None, children=None):
        super().__init__(name, display_name, included)
        self.children = children


class BackupSet(Object):

    def __init__(self, name, directory_tree=None, filter_rules=None, defaults_dirs=None,
                 template_dirs=None, enabled=True, boolean_function=None, comment=None):
        self._classname = self.__class__.__name__  # pylint: disable=protected-access
        self.name = name
        self.isEnabled = enabled
        self.directoryTree = directory_tree if directory_tree else DirectoryEntryFactory.root(True)
        self.booleanFunction = boolean_function if boolean_function else BooleanFunction.AND
        self.templateDirectories = template_dirs
        self.defaultDirs = defaults_dirs
        self.comment = comment
        self.filterRules = filter_rules


class FilterBackupSet(BackupSet):
    pass


class ApplicationBackupSet(BackupSet):

    def __init__(self, apps):

        comment = 'Backup all the specified applications'
        name = 'Applications'

        if apps == Application.All:
            super().__init__(name=name, directory_tree=DirEntry(name, included=True), comment=comment)
        else:
            directory_tree = DirEntry(name, included=False, children=[DirEntry(app, included=True) for app in apps])
            super().__init__(name=name, directory_tree=directory_tree, comment=comment)

        self._classname = None


class TaskSchedule(Object):

    def __init__(self):
        self._classname = 'TaskSchedule'  # pylint: disable=protected-access
        self.mode = None


class BackupScheduleBuilder:

    @staticmethod
    def interval(hours=None, minutes=None):
        """
        Schedule backup to periodically, defaults to 24 hours

        :param int hours: Hours
        :param int minutes: Minutes
        """
        param = TaskSchedule()
        param.mode = ScheduleType.Interval
        param.interval = Object()  # pylint: disable=attribute-defined-outside-init
        param.interval.hours = hours if hours is not None else 24
        param.interval.minutes = minutes if minutes is not None else 0
        return param

    @staticmethod
    def window(time_range):
        """
        Schedule backup to run at a specific time

        :param cterasdk.common.types.TimeRange time_range: Time range
        """
        param = TaskSchedule()
        param.mode = ScheduleType.Window
        param.window = time_range  # pylint: disable=attribute-defined-outside-init
        return param


class TimeRange:
    """ Class representing a task schedule """

    def __init__(self):
        self.param = Object()
        self.param._classname = 'TimeRange'  # pylint: disable=protected-access
        self.param.start = None
        self.param.end = None
        self.param.days = None
        self.param.termOnEnd = False

    def start(self, start):
        """
        Start time

        :param str start: A military time string 'hh:mm:ss' or a datetime object
        """
        self.param.start = TimeRange._infer_time_range(start)
        return self

    def end(self, end):
        """
        End time

        :param str end: A military time string 'hh:mm:ss' or a datetime object
        """
        self.param.end = TimeRange._infer_time_range(end)
        return self

    def days(self, days):
        """
        Set days

        :param list[cterasdk.common.enum.DayOfWeek] days: A list of days
        """
        self.param.days = days
        return self

    def terminate_at_endtime(self):
        """
        Terminate at end time, defaults to teminate on completion.
        """
        self.param.termOnEnd = True
        return self

    def build(self):
        """
        Build the time range
        """
        if self.param.termOnEnd and self.param.end is None:
            raise ValueError('End time required')
        if self.param.start is None:
            raise ValueError('Start time required')
        return self.param

    @staticmethod
    def _infer_time_range(time):
        if isinstance(time, datetime):
            return df_military_time(time)
        if isinstance(time, str):
            match = re.search('^[012][0-9]:[0-5][0-9]:[0-5][0-9]$', time)
            if match:
                return match.group(0)
            raise ValueError("Invalid time format. Expected 'hh:mm:ss'")
        raise ValueError("Invalid format. Expected 'datetime' or 'str'")

    def __str__(self):
        return str(self.param)


class ADDomainIDMapping(Object):
    """
    Base Class for Directory Service ID Mapping

    :ivar str domain: The domain flat name
    :param int start: The minimum id to use for mapping
    :param int end: The maximum id to use for mapping
    """
    def __init__(self, domain, start, end):
        self._classname = 'ADDomainIDMapping'
        self.domainFlatName = domain
        self.minID = start
        self.maxID = end

import re
from datetime import datetime

from .object import Object
from .utils import df_military_time, day_of_week
from .enum import FileCriteria, BooleanFunction, Application, ScheduleType


class PolicyRule:

    def __init__(self, assignment, criteria):
        self.assignment = assignment
        self.criteria = criteria


class PolicyRuleConverter:

    @staticmethod
    def convert(rule, classname, property_name, assignment=None):
        param = Object()
        param._classname = classname  # pylint: disable=protected-access
        setattr(param, property_name, assignment if assignment else rule.assignment)
        param.filterRule = rule.criteria
        return param


class Operator(Object):

    def __init__(self, right):
        self._classname = self.__class__.__name__  # pylint: disable=protected-access
        self.right = right


class IsOperator(Operator):
    pass


class BeginsWithOperator(Operator):
    pass


class EndsWithOperator(Operator):
    pass


class ContainsOperator(Operator):
    pass


class IsOneOfOperator(Operator):
    pass


class LessThanOperator(Operator):
    pass


class MoreThanOperator(Operator):
    pass


class BeforeOperator(Operator):
    pass


class AfterOperator(Operator):
    pass


class AdvancedFilterRule(Object):

    def __init__(self, classname, field, operator):
        self._classname = self.__class__.__name__  # pylint: disable=protected-access
        self.className = classname
        self.fieldName = field
        self.operator = operator


class CriteriaBuilder:

    def __init__(self, criteria_type, criteria_field):
        self.criteria_type = criteria_type
        self.criteria_field = criteria_field
        self.operator = None

    def build(self):
        return AdvancedFilterRule(self.criteria_type, self.criteria_field, self.operator)


class ListCriteriaBuilder(CriteriaBuilder):

    def include(self, values):
        self.operator = IsOneOfOperator(values)
        return self


class IntegerCriteriaBuilder(CriteriaBuilder):

    def less_than(self, value):
        self.operator = LessThanOperator(value)
        return self

    def more_than(self, value):
        self.operator = MoreThanOperator(value)
        return self


class DateTimeCriteriaBuilder(CriteriaBuilder):

    def before(self, value):
        self.operator = BeforeOperator(value)
        return self

    def after(self, value):
        self.operator = AfterOperator(value)
        return self


class StringCriteriaBuilder(CriteriaBuilder):

    def equals(self, value):
        self.operator = IsOperator(value)
        return self

    def startswith(self, value):
        self.operator = BeginsWithOperator(value)
        return self

    def endswith(self, value):
        self.operator = EndsWithOperator(value)
        return self

    def contains(self, value):
        self.operator = ContainsOperator(value)
        return self

    def isoneof(self, values):
        self.operator = IsOneOfOperator(values)
        return self


class ThrottlingRule:
    """
    Throttling Rule

    :ivar int upload: Throttling rate upstream (Kilobits)
    :ivar int download: Throttling rate downstream (Kilobits)
    :ivar str start: Start time
    :ivar str end: End time
    :ivar list[str] days: Days
    """

    def __init__(self):
        self.upload = None
        self.download = None
        self.schedule = None

    def to_server_object(self):
        param = Object()
        param._classname = 'SyncThrottlingSettings'  # pylint: disable=protected-access
        param.inKbitsPerSecond = self.download
        param.outKbitsPerSecond = self.upload
        param.schedule = self.schedule
        return param

    @staticmethod
    def from_server_object(param):
        r = ThrottlingRule()
        r.download = param.inKbitsPerSecond
        r.upload = param.outKbitsPerSecond
        r.schedule = param.schedule
        return r

    def __str__(self):
        return str(dict(upload_kbps=self.upload, download_kbps=self.download,
                        start=self.schedule.start, end=self.schedule.end, days=[day_of_week(day) for day in self.schedule.days]))


class ThrottlingRuleBuilder:
    """
    Bandwidth Throttling Rule Builder
    """

    def __init__(self):
        self.param = ThrottlingRule()

    def upload(self, kbps):
        """
        Throttle bandwidth upstream

        :param int kbps: Kilobits per second
        """
        self.param.upload = kbps
        return self

    def download(self, kbps):
        """
        Throttle bandwidth downstream

        :param int kbps: Kilobits per second
        """
        self.param.download = kbps
        return self

    def schedule(self, schedule):
        """
        Set the throttling rule schedule

        :param cterasdk.common.types.TimeRange schedule: Schedule
        """
        self.param.schedule = schedule
        return self

    def build(self):
        """
        Build the throttling rule
        """
        errors = [k for k, v in self.param.__dict__.items() if v is None]
        if errors:
            raise ValueError(f'No value for required field: {errors}')
        return self.param


class FileFilterBuilder:

    Type = 'File'

    @staticmethod
    def extensions():
        """Filter files by extension"""
        return ListCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Type)

    @staticmethod
    def names():
        """Filter files by names"""
        return ListCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Name)

    @staticmethod
    def name():
        """Filter files by name pattern"""
        return StringCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Name)

    @staticmethod
    def paths():
        """Filter files by path"""
        return ListCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Path)

    @staticmethod
    def path():
        """Filter files by path pattern"""
        return StringCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Path)

    @staticmethod
    def size():
        """Filter files by size"""
        return IntegerCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Size)

    @staticmethod
    def last_modified():
        """Filter files by last modification date"""
        return DateTimeCriteriaBuilder(FileFilterBuilder.Type, FileCriteria.Modified)


class DirectoryEntryFactory:

    @staticmethod
    def root(included):
        return DirEntry('root', included=included)


class FileEntry(Object):

    def __init__(self, name, display_name=None, included=None):
        self._classname = self.__class__.__name__  # pylint: disable=protected-access
        self.name = name
        self.displayName = display_name
        self.isIncluded = included


class DirEntry(FileEntry):

    def __init__(self, name, display_name=None, included=None, children=None):
        super().__init__(name, display_name, included)
        self.children = children


class BackupSet(Object):

    def __init__(self, name, directory_tree=None, filter_rules=None, defaults_dirs=None,
                 template_dirs=None, enabled=True, boolean_function=None, comment=None):
        self._classname = self.__class__.__name__  # pylint: disable=protected-access
        self.name = name
        self.isEnabled = enabled
        self.directoryTree = directory_tree if directory_tree else DirectoryEntryFactory.root(True)
        self.booleanFunction = boolean_function if boolean_function else BooleanFunction.AND
        self.templateDirectories = template_dirs
        self.defaultDirs = defaults_dirs
        self.comment = comment
        self.filterRules = filter_rules


class FilterBackupSet(BackupSet):
    pass


class ApplicationBackupSet(BackupSet):

    def __init__(self, apps):

        comment = 'Backup all the specified applications'
        name = 'Applications'

        if apps == Application.All:
            super().__init__(name=name, directory_tree=DirEntry(name, included=True), comment=comment)
        else:
            directory_tree = DirEntry(name, included=False, children=[DirEntry(app, included=True) for app in apps])
            super().__init__(name=name, directory_tree=directory_tree, comment=comment)

        self._classname = None


class TaskSchedule(Object):

    def __init__(self):
        self._classname = 'TaskSchedule'  # pylint: disable=protected-access
        self.mode = None


class BackupScheduleBuilder:

    @staticmethod
    def interval(hours=None, minutes=None):
        """
        Schedule backup to periodically, defaults to 24 hours

        :param int hours: Hours
        :param int minutes: Minutes
        """
        param = TaskSchedule()
        param.mode = ScheduleType.Interval
        param.interval = Object()  # pylint: disable=attribute-defined-outside-init
        param.interval.hours = hours if hours is not None else 24
        param.interval.minutes = minutes if minutes is not None else 0
        return param

    @staticmethod
    def window(time_range):
        """
        Schedule backup to run at a specific time

        :param cterasdk.common.types.TimeRange time_range: Time range
        """
        param = TaskSchedule()
        param.mode = ScheduleType.Window
        param.window = time_range  # pylint: disable=attribute-defined-outside-init
        return param


class TimeRange:
    """ Class representing a task schedule """

    def __init__(self):
        self.param = Object()
        self.param._classname = 'TimeRange'  # pylint: disable=protected-access
        self.param.start = None
        self.param.end = None
        self.param.days = None
        self.param.termOnEnd = False

    def start(self, start):
        """
        Start time

        :param str start: A military time string 'hh:mm:ss' or a datetime object
        """
        self.param.start = TimeRange._infer_time_range(start)
        return self

    def end(self, end):
        """
        End time

        :param str end: A military time string 'hh:mm:ss' or a datetime object
        """
        self.param.end = TimeRange._infer_time_range(end)
        return self

    def days(self, days):
        """
        Set days

        :param list[cterasdk.common.enum.DayOfWeek] days: A list of days
        """
        self.param.days = days
        return self

    def terminate_at_endtime(self):
        """
        Terminate at end time, defaults to teminate on completion.
        """
        self.param.termOnEnd = True
        return self

    def build(self):
        """
        Build the time range
        """
        if self.param.termOnEnd and self.param.end is None:
            raise ValueError('End time required')
        if self.param.start is None:
            raise ValueError('Start time required')
        return self.param

    @staticmethod
    def _infer_time_range(time):
        if isinstance(time, datetime):
            return df_military_time(time)
        if isinstance(time, str):
            match = re.search('^[012][0-9]:[0-5][0-9]:[0-5][0-9]$', time)
            if match:
                return match.group(0)
            raise ValueError("Invalid time format. Expected 'hh:mm:ss'")
        raise ValueError("Invalid format. Expected 'datetime' or 'str'")

    def __str__(self):
        return str(self.param)


class ADDomainIDMapping(Object):
    """
    Base Class for Directory Service ID Mapping

    :ivar str domain: The domain flat name
    :param int start: The minimum id to use for mapping
    :param int end: The maximum id to use for mapping
    """
    def __init__(self, domain, start, end):
        self._classname = 'ADDomainIDMapping'
        self.domainFlatName = domain
        self.minID = start
        self.maxID = end

# pylint: disable=protected-access # pylint: disable=protected-access # pylint: disable=protected-access Throttling Rule      :ivar int upload: Throttling rate upstream (Kilobits)     :ivar int download: Throttling rate downstream (Kilobits)     :ivar str start: Start time     :ivar str end: End time     :ivar list[str] days: Days # pylint: disable=protected-access Bandwidth Throttling Rule Builder Throttle bandwidth upstream          :param int kbps: Kilobits per second Throttle bandwidth downstream          :param int kbps: Kilobits per second Set the throttling rule schedule          :param cterasdk.common.types.TimeRange schedule: Schedule Build the throttling rule Filter files by extension Filter files by names Filter files by name pattern Filter files by path Filter files by path pattern Filter files by size Filter files by last modification date # pylint: disable=protected-access # pylint: disable=protected-access # pylint: disable=protected-access Schedule backup to periodically, defaults to 24 hours          :param int hours: Hours         :param int minutes: Minutes # pylint: disable=attribute-defined-outside-init Schedule backup to run at a specific time          :param cterasdk.common.types.TimeRange time_range: Time range # pylint: disable=attribute-defined-outside-init Class representing a task schedule # pylint: disable=protected-access Start time          :param str start: A military time string 'hh:mm:ss' or a datetime object End time          :param str end: A military time string 'hh:mm:ss' or a datetime object Set days          :param list[cterasdk.common.enum.DayOfWeek] days: A list of days Terminate at end time, defaults to teminate on completion. Build the time range Base Class for Directory Service ID Mapping      :ivar str domain: The domain flat name     :param int start: The minimum id to use for mapping     :param int end: The maximum id to use for mapping