# -*- coding: utf-8 -*-
# MinIO Python Library for Amazon S3 Compatible Cloud Storage, (C)
# 2020 MinIO, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pylint: disable=too-many-lines

"""
Response of ListBuckets, ListObjects, ListObjectsV2 and ListObjectVersions API.
"""

from __future__ import absolute_import

import base64
import datetime
import json
from collections import OrderedDict
from enum import Enum
from urllib.parse import unquote_plus
from xml.etree import ElementTree as ET

from .credentials import Credentials
from .helpers import check_bucket_name
from .signer import get_credential_string, post_presign_v4
from .time import from_iso8601utc, to_amz_date, to_iso8601utc
from .xml import find, findall, findtext

try:
    from json.decoder import JSONDecodeError
except ImportError:
    JSONDecodeError = ValueError


class Bucket:
    """Bucket information."""

    def __init__(self, name, creation_date):
        self._name = name
        self._creation_date = creation_date

    @property
    def name(self):
        """Get name."""
        return self._name

    @property
    def creation_date(self):
        """Get creation date."""
        return self._creation_date

    def __repr__(self):
        return f"{type(self).__name__}('{self.name}')"

    def __str__(self):
        return self.name

    def __eq__(self, other):
        if isinstance(other, Bucket):
            return self.name == other.name
        if isinstance(other, str):
            return self.name == other
        return NotImplemented

    def __hash__(self):
        return hash(self.name)


class ListAllMyBucketsResult:
    """LissBuckets API result."""

    def __init__(self, buckets):
        self._buckets = buckets

    @property
    def buckets(self):
        """Get buckets."""
        return self._buckets

    @classmethod
    def fromxml(cls, element):
        """Create new object with values from XML element."""
        element = find(element, "Buckets")
        buckets = []
        if element is not None:
            elements = findall(element, "Bucket")
            for bucket in elements:
                name = findtext(bucket, "Name", True)
                creation_date = findtext(bucket, "CreationDate")
                if creation_date:
                    creation_date = from_iso8601utc(creation_date)
                buckets.append(Bucket(name, creation_date))
        return cls(buckets)


class Object:
    """Object information."""

    def __init__(self,  # pylint: disable=too-many-arguments
                 bucket_name,
                 object_name,
                 last_modified=None, etag=None,
                 size=None, metadata=None,
                 version_id=None, is_latest=None, storage_class=None,
                 owner_id=None, owner_name=None, content_type=None,
                 is_delete_marker=False):
        self._bucket_name = bucket_name
        self._object_name = object_name
        self._last_modified = last_modified
        self._etag = etag
        self._size = size
        self._metadata = metadata
        self._version_id = version_id
        self._is_latest = is_latest
        self._storage_class = storage_class
        self._owner_id = owner_id
        self._owner_name = owner_name
        self._content_type = content_type
        self._is_delete_marker = is_delete_marker

    @property
    def bucket_name(self):
        """Get bucket name."""
        return self._bucket_name

    @property
    def object_name(self):
        """Get object name."""
        return self._object_name

    @property
    def is_dir(self):
        """Get whether this key is a directory."""
        return self._object_name.endswith("/")

    @property
    def last_modified(self):
        """Get last modified time."""
        return self._last_modified

    @property
    def etag(self):
        """Get etag."""
        return self._etag

    @property
    def size(self):
        """Get size."""
        return self._size

    @property
    def metadata(self):
        """Get metadata."""
        return self._metadata

    @property
    def version_id(self):
        """Get version ID."""
        return self._version_id

    @property
    def is_latest(self):
        """Get is-latest flag."""
        return self._is_latest

    @property
    def storage_class(self):
        """Get storage class."""
        return self._storage_class

    @property
    def owner_id(self):
        """Get owner ID."""
        return self._owner_id

    @property
    def owner_name(self):
        """Get owner name."""
        return self._owner_name

    @property
    def is_delete_marker(self):
        """Get whether this key is a delete marker."""
        return self._is_delete_marker

    @property
    def content_type(self):
        """Get content type."""
        return self._content_type

    @classmethod
    def fromxml(cls, element, bucket_name, is_delete_marker=False,
                encoding_type=None):
        """Create new object with values from XML element."""
        tag = findtext(element, "LastModified")
        last_modified = None if tag is None else from_iso8601utc(tag)

        tag = findtext(element, "ETag")
        etag = None if tag is None else tag.replace('"', "")

        tag = findtext(element, "Size")
        size = None if tag is None else int(tag)

        tag = find(element, "Owner")
        owner_id, owner_name = (
            (None, None) if tag is None
            else (findtext(tag, "ID"), findtext(tag, "DisplayName"))
        )

        tag = find(element, "UserMetadata") or []
        metadata = {}
        for child in tag:
            key = child.tag.split("}")[1] if "}" in child.tag else child.tag
            metadata[key] = child.text

        object_name = findtext(element, "Key", True)
        if encoding_type == "url":
            object_name = unquote_plus(object_name)

        return cls(
            bucket_name,
            object_name,
            last_modified=last_modified,
            etag=etag,
            size=size,
            version_id=findtext(element, "VersionId"),
            is_latest=findtext(element, "IsLatest"),
            storage_class=findtext(element, "StorageClass"),
            owner_id=owner_id,
            owner_name=owner_name,
            metadata=metadata,
            is_delete_marker=is_delete_marker,
        )


def parse_list_objects(response, bucket_name=None):
    """Parse ListObjects/ListObjectsV2/ListObjectVersions response."""
    element = ET.fromstring(response.data.decode())
    bucket_name = findtext(element, "Name", True)
    encoding_type = findtext(element, "EncodingType")
    elements = findall(element, "Contents")
    objects = [
        Object.fromxml(tag, bucket_name, encoding_type=encoding_type)
        for tag in elements
    ]
    marker = objects[-1].object_name if objects else None

    elements = findall(element, "Version")
    objects += [
        Object.fromxml(tag, bucket_name, encoding_type=encoding_type)
        for tag in elements
    ]

    elements = findall(element, "CommonPrefixes")
    objects += [
        Object(
            bucket_name, unquote_plus(findtext(tag, "Prefix", True))
            if encoding_type == "url" else findtext(tag, "Prefix", True)
        ) for tag in elements
    ]

    elements = findall(element, "DeleteMarker")
    objects += [
        Object.fromxml(tag, bucket_name, is_delete_marker=True,
                       encoding_type=encoding_type)
        for tag in elements
    ]

    is_truncated = (findtext(element, "IsTruncated") or "").lower() == "true"
    key_marker = findtext(element, "NextKeyMarker")
    if key_marker and encoding_type == "url":
        key_marker = unquote_plus(key_marker)
    version_id_marker = findtext(element, "NextVersionIdMarker")
    continuation_token = findtext(element, "NextContinuationToken")
    if key_marker is not None:
        continuation_token = key_marker
    if continuation_token is None:
        continuation_token = findtext(element, "NextMarker")
        if continuation_token and encoding_type == "url":
            continuation_token = unquote_plus(continuation_token)
    if continuation_token is None and is_truncated:
        continuation_token = marker
    return objects, is_truncated, continuation_token, version_id_marker


class CompleteMultipartUploadResult:
    """CompleteMultipartUpload API result."""

    def __init__(self, response):
        element = ET.fromstring(response.data.decode())
        self._bucket_name = findtext(element, "Bucket")
        self._object_name = findtext(element, "Key")
        self._location = findtext(element, "Location")
        self._etag = findtext(element, "ETag")
        if self._etag:
            self._etag = self._etag.replace('"', "")
        self._version_id = response.headers.get("x-amz-version-id")
        self._http_headers = response.headers

    @property
    def bucket_name(self):
        """Get bucket name."""
        return self._bucket_name

    @property
    def object_name(self):
        """Get object name."""
        return self._object_name

    @property
    def location(self):
        """Get location."""
        return self._location

    @property
    def etag(self):
        """Get etag."""
        return self._etag

    @property
    def version_id(self):
        """Get version ID."""
        return self._version_id

    @property
    def http_headers(self):
        """Get HTTP headers."""
        return self._http_headers


class Part:
    """Part information of a multipart upload."""

    def __init__(self, part_number, etag, last_modified=None, size=None):
        self._part_number = part_number
        self._etag = etag
        self._last_modified = last_modified
        self._size = size

    @property
    def part_number(self):
        """Get part number. """
        return self._part_number

    @property
    def etag(self):
        """Get etag."""
        return self._etag

    @property
    def last_modified(self):
        """Get last-modified."""
        return self._last_modified

    @property
    def size(self):
        """Get size."""
        return self._size

    @classmethod
    def fromxml(cls, element):
        """Create new object with values from XML element."""
        part_number = findtext(element, "PartNumber", True)
        etag = findtext(element, "ETag", True)
        etag = etag.replace('"', "")
        tag = findtext(element, "LastModified")
        last_modified = None if tag is None else from_iso8601utc(tag)
        size = findtext(element, "Size")
        if size:
            size = int(size)
        return cls(part_number, etag, last_modified, size)


class ListPartsResult:
    """ListParts API result."""

    def __init__(self, response):
        element = ET.fromstring(response.data.decode())
        self._bucket_name = findtext(element, "Bucket")
        self._object_name = findtext(element, "Key")
        tag = find(element, "Initiator")
        self._initiator_id = (
            None if tag is None else findtext(tag, "ID")
        )
        self._initiator_name = (
            None if tag is None else findtext(tag, "DisplayName")
        )
        tag = find(element, "Owner")
        self._owner_id = (
            None if tag is None else findtext(tag, "ID")
        )
        self._owner_name = (
            None if tag is None else findtext(tag, "DisplayName")
        )
        self._storage_class = findtext(element, "StorageClass")
        self._part_number_marker = findtext(element, "PartNumberMarker")
        self._next_part_number_marker = findtext(
            element, "NextPartNumberMarker",
        )
        if self._next_part_number_marker:
            self._next_part_number_marker = int(self._next_part_number_marker)
        self._max_parts = findtext(element, "MaxParts")
        if self._max_parts:
            self._max_parts = int(self._max_parts)
        self._is_truncated = findtext(element, "IsTruncated")
        self._is_truncated = (
            self._is_truncated is not None and
            self._is_truncated.lower() == "true"
        )
        self._parts = [Part.fromxml(tag) for tag in findall(element, "Part")]

    @property
    def bucket_name(self):
        """Get bucket name."""
        return self._bucket_name

    @property
    def object_name(self):
        """Get object name."""
        return self._object_name

    @property
    def initiator_id(self):
        """Get initiator ID."""
        return self._initiator_id

    @property
    def initator_name(self):
        """Get initiator name."""
        return self._initiator_name

    @property
    def owner_id(self):
        """Get owner ID."""
        return self._owner_id

    @property
    def owner_name(self):
        """Get owner name."""
        return self._owner_name

    @property
    def storage_class(self):
        """Get storage class."""
        return self._storage_class

    @property
    def part_number_marker(self):
        """Get part number marker."""
        return self._part_number_marker

    @property
    def next_part_number_marker(self):
        """Get next part number marker."""
        return self._next_part_number_marker

    @property
    def max_parts(self):
        """Get max parts."""
        return self._max_parts

    @property
    def is_truncated(self):
        """Get is-truncated flag."""
        return self._is_truncated

    @property
    def parts(self):
        """Get parts."""
        return self._parts


class Upload:
    """ Upload information of a multipart upload."""

    def __init__(self, element, encoding_type=None):
        self._encoding_type = encoding_type
        self._object_name = findtext(element, "Key", True)
        self._object_name = (
            unquote_plus(self._object_name) if self._encoding_type == "url"
            else self._object_name
        )
        self._upload_id = findtext(element, "UploadId")
        tag = find(element, "Initiator")
        self._initiator_id = (
            None if tag is None else findtext(tag, "ID")
        )
        self._initiator_name = (
            None if tag is None else findtext(tag, "DisplayName")
        )
        tag = find(element, "Owner")
        self._owner_id = (
            None if tag is None else findtext(tag, "ID")
        )
        self._owner_name = (
            None if tag is None else findtext(tag, "DisplayName")
        )
        self._storage_class = findtext(element, "StorageClass")
        self._initiated_time = findtext(element, "Initiated")
        if self._initiated_time:
            self._initiated_time = from_iso8601utc(self._initiated_time)

    @property
    def object_name(self):
        """Get object name."""
        return self._object_name

    @property
    def initiator_id(self):
        """Get initiator ID."""
        return self._initiator_id

    @property
    def initator_name(self):
        """Get initiator name."""
        return self._initiator_name

    @property
    def owner_id(self):
        """Get owner ID."""
        return self._owner_id

    @property
    def owner_name(self):
        """Get owner name."""
        return self._owner_name

    @property
    def storage_class(self):
        """Get storage class."""
        return self._storage_class

    @property
    def upload_id(self):
        """Get upload ID."""
        return self._upload_id

    @property
    def initiated_time(self):
        """Get initiated time."""
        return self._initiated_time


class ListMultipartUploadsResult:
    """ListMultipartUploads API result."""

    def __init__(self, response):
        element = ET.fromstring(response.data.decode())
        self._encoding_type = findtext(element, "EncodingType")
        self._bucket_name = findtext(element, "Bucket")
        self._key_marker = findtext(element, "KeyMarker")
        if self._key_marker:
            self._key_marker = (
                unquote_plus(self._key_marker) if self._encoding_type == "url"
                else self._key_marker
            )
        self._upload_id_marker = findtext(element, "UploadIdMarker")
        self._next_key_marker = findtext(element, "NextKeyMarker")
        if self._next_key_marker:
            self._next_key_marker = (
                unquote_plus(self._next_key_marker)
                if self._encoding_type == "url" else self._next_key_marker
            )
        self._next_upload_id_marker = findtext(element, "NextUploadIdMarker")
        self._max_uploads = findtext(element, "MaxUploads")
        if self._max_uploads:
            self._max_uploads = int(self._max_uploads)
        self._is_truncated = findtext(element, "IsTruncated")
        self._is_truncated = (
            self._is_truncated is not None and
            self._is_truncated.lower() == "true"
        )
        self._uploads = [
            Upload(tag, self._encoding_type)
            for tag in findall(element, "Upload")
        ]

    @property
    def bucket_name(self):
        """Get bucket name."""
        return self._bucket_name

    @property
    def key_marker(self):
        """Get key marker."""
        return self._key_marker

    @property
    def upload_id_marker(self):
        """Get upload ID marker."""
        return self._upload_id_marker

    @property
    def next_key_marker(self):
        """Get next key marker."""
        return self._next_key_marker

    @property
    def next_upload_id_marker(self):
        """Get next upload ID marker."""
        return self._next_upload_id_marker

    @property
    def max_uploads(self):
        """Get max uploads."""
        return self._max_uploads

    @property
    def is_truncated(self):
        """Get is-truncated flag."""
        return self._is_truncated

    @property
    def encoding_type(self):
        """Get encoding type."""
        return self._encoding_type

    @property
    def uploads(self):
        """Get uploads."""
        return self._uploads


_RESERVED_ELEMENTS = (
    "bucket",
    "x-amz-algorithm",
    "x-amz-credential",
    "x-amz-date",
    "policy",
    "x-amz-signature",
)
_EQ = "eq"
_STARTS_WITH = "starts-with"
_ALGORITHM = "AWS4-HMAC-SHA256"


def _trim_dollar(value):
    """Trim dollar character if present."""
    return value[1:] if value.startswith("$") else value


class PostPolicy:
    """
    Post policy information to be used to generate presigned post policy
    form-data. Condition elements and respective condition for Post policy
    is available at
    https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-HTTPPOSTConstructPolicy.html#sigv4-PolicyConditions
    """

    def __init__(self, bucket_name, expiration):
        check_bucket_name(bucket_name)
        if not isinstance(expiration, datetime.datetime):
            raise ValueError("expiration must be datetime.datetime type")
        self._bucket_name = bucket_name
        self._expiration = expiration
        self._conditions = OrderedDict()
        self._conditions[_EQ] = OrderedDict()
        self._conditions[_STARTS_WITH] = OrderedDict()
        self._lower_limit = None
        self._upper_limit = None

    def add_equals_condition(self, element, value):
        """Add equals condition of an element and value."""
        if not element:
            raise ValueError("condition element cannot be empty")
        element = _trim_dollar(element)
        if (
                element in [
                    "success_action_redirect",
                    "redirect",
                    "content-length-range",
                ]
        ):
            raise ValueError(element + " is unsupported for equals condition")
        if element in _RESERVED_ELEMENTS:
            raise ValueError(element + " cannot be set")
        self._conditions[_EQ][element] = value

    def remove_equals_condition(self, element):
        """Remove previously set equals condition of an element."""
        if not element:
            raise ValueError("condition element cannot be empty")
        self._conditions[_EQ].pop(element)

    def add_starts_with_condition(self, element, value):
        """
        Add starts-with condition of an element and value. Value set to empty
        string does matching any content condition.
        """
        if not element:
            raise ValueError("condition element cannot be empty")
        element = _trim_dollar(element)
        if (
                element in ["success_action_status", "content-length-range"] or
                (
                    element.startswith("x-amz-") and
                    not element.startswith("x-amz-meta-")
                )
        ):
            raise ValueError(
                f"{element} is unsupported for starts-with condition",
            )
        if element in _RESERVED_ELEMENTS:
            raise ValueError(element + " cannot be set")
        self._conditions[_STARTS_WITH][element] = value

    def remove_starts_with_condition(self, element):
        """Remove previously set starts-with condition of an element."""
        if not element:
            raise ValueError("condition element cannot be empty")
        self._conditions[_STARTS_WITH].pop(element)

    def add_content_length_range_condition(  # pylint: disable=invalid-name
            self, lower_limit, upper_limit):
        """Add content-length-range condition with lower and upper limits."""
        if lower_limit < 0:
            raise ValueError("lower limit cannot be negative number")
        if upper_limit < 0:
            raise ValueError("upper limit cannot be negative number")
        if lower_limit > upper_limit:
            raise ValueError("lower limit cannot be greater than upper limit")
        self._lower_limit = lower_limit
        self._upper_limit = upper_limit

    def remove_content_length_range_condition(  # pylint: disable=invalid-name
            self):
        """Remove previously set content-length-range condition."""
        self._lower_limit = None
        self._upper_limit = None

    def form_data(self, creds, region):
        """
        Return form-data of this post policy. The returned dict contains
        x-amz-algorithm, x-amz-credential, x-amz-security-token, x-amz-date,
        policy and x-amz-signature.
        """
        if not isinstance(creds, Credentials):
            raise ValueError("credentials must be Credentials type")
        if not region:
            raise ValueError("region cannot be empty")
        if (
                "key" not in self._conditions[_EQ] and
                "key" not in self._conditions[_STARTS_WITH]
        ):
            raise ValueError("key condition must be set")

        policy = OrderedDict()
        policy["expiration"] = to_iso8601utc(self._expiration)
        policy["conditions"] = [[_EQ, "$bucket", self._bucket_name]]
        for cond_key, conditions in self._conditions.items():
            for key, value in conditions.items():
                policy["conditions"].append([cond_key, "$"+key, value])
        if self._lower_limit is not None and self._upper_limit is not None:
            policy["conditions"].append(
                ["content-length-range", self._lower_limit, self._upper_limit],
            )
        utcnow = datetime.datetime.utcnow()
        credential = get_credential_string(creds.access_key, utcnow, region)
        amz_date = to_amz_date(utcnow)
        policy["conditions"].append([_EQ, "$x-amz-algorithm", _ALGORITHM])
        policy["conditions"].append([_EQ, "$x-amz-credential", credential])
        if creds.session_token:
            policy["conditions"].append(
                [_EQ, "$x-amz-security-token", creds.session_token],
            )
        policy["conditions"].append([_EQ, "$x-amz-date", amz_date])

        policy = base64.b64encode(json.dumps(policy).encode()).decode("utf-8")
        signature = post_presign_v4(
            policy, creds.secret_key, utcnow, region,
        )
        form_data = {
            "x-amz-algorithm": _ALGORITHM,
            "x-amz-credential": credential,
            "x-amz-date": amz_date,
            "policy": policy,
            "x-amz-signature": signature,
        }
        if creds.session_token:
            form_data["x-amz-security-token"] = creds.session_token
        return form_data

    @property
    def bucket_name(self):
        """Get bucket name."""
        return self._bucket_name


def parse_copy_object(response):
    """Parse CopyObject/UploadPartCopy response."""
    element = ET.fromstring(response.data.decode())
    etag = findtext(element, "ETag")
    if etag:
        etag = etag.replace('"', "")
    last_modified = findtext(element, "LastModified")
    if last_modified:
        last_modified = from_iso8601utc(last_modified)
    return etag, last_modified


class EventIterable:
    """Context manager friendly event iterable."""

    def __init__(self, func):
        self._func = func
        self._response = None

    def _close_response(self):
        """Close response."""
        if self._response:
            self._response.close()
            self._response.release_conn()
            self._response = None

    def __iter__(self):
        return self

    def _get_records(self):
        """Get event records from response stream."""
        try:
            line = self._response.readline().strip()
            if not line:
                return None
            if hasattr(line, 'decode'):
                line = line.decode()
            event = json.loads(line)
            if event['Records']:
                return event
        except (StopIteration, JSONDecodeError):
            self._close_response()
        return None

    def __next__(self):
        records = None
        while not records:
            if not self._response or self._response.closed:
                self._response = self._func()
            records = self._get_records()
        return records

    def __enter__(self):
        return self

    def __exit__(self, exc_type, value, traceback):
        self._close_response()


class PeerSite:
    """Represents a cluster/site to be added to the set of replicated sites."""

    def __init__(self, name, endpoint, access_key, secret_key):
        self._name = name
        self._endpoint = endpoint
        self._access_key = access_key
        self._secret_key = secret_key

    def to_dict(self):
        """Convert to dictionary."""
        return {
            "name": self._name,
            "endpoints": self._endpoint,
            "accessKey": self._access_key,
            "secretKey": self._secret_key,
        }


class SiteReplicationStatusOptions:
    """Represents site replication status options."""
    ENTITY_TYPE = Enum(
        "ENTITY_TYPE",
        {
            "BUCKET": "bucket",
            "POLICY": "policy",
            "USER": "user",
            "GROUP": "group",
        },
    )

    def __init__(self):
        self._buckets = False
        self._policies = False
        self._users = False
        self._groups = False
        self._metrics = False
        self._entity = None
        self._entity_value = None
        self._show_deleted = False

    @property
    def buckets(self):
        """Get buckets."""
        return self._buckets

    @buckets.setter
    def buckets(self, value):
        """Set buckets."""
        self._buckets = value

    @property
    def policies(self):
        """Get policies."""
        return self._policies

    @policies.setter
    def policies(self, value):
        """Set policies."""
        self._policies = value

    @property
    def users(self):
        """Get users."""
        return self._users

    @users.setter
    def users(self, value):
        """Set users."""
        self._users = value

    @property
    def groups(self):
        """Get groups."""
        return self._groups

    @groups.setter
    def groups(self, value):
        """Set groups."""
        self._groups = value

    @property
    def metrics(self):
        """Get metrics."""
        return self._metrics

    @metrics.setter
    def metrics(self, value):
        """Set metrics."""
        self._metrics = value

    @property
    def entity(self):
        """Get entity."""
        return self._entity

    @entity.setter
    def entity(self, value):
        """Set entity."""
        self._entity = value

    @property
    def entity_value(self):
        """Get entity value."""
        return self._entity_value

    @entity_value.setter
    def entity_value(self, value):
        """Set entity value."""
        self._entity_value = value

    @property
    def show_deleted(self):
        """Get show deleted."""
        return self._show_deleted

    @show_deleted.setter
    def show_deleted(self, value):
        """Set show deleted."""
        self._show_deleted = value

    def to_query_params(self):
        """Convert this options to query parameters."""
        params = {
            "buckets": str(self._buckets).lower(),
            "policies": str(self._policies).lower(),
            "users": str(self._users).lower(),
            "groups": str(self._groups).lower(),
            "metrics": str(self._metrics).lower(),
            "showDeleted": str(self._show_deleted).lower(),
        }
        if self._entity and self._entity_value:
            params["entityvalue"] = self._entity_value
            params["entity"] = self._entity.value
        return params


class PeerInfo:
    """Site replication peer information."""

    def __init__(self, deployment_id, endpoint, bucket_bandwidth_limit,
                 bucket_bandwidth_set):
        self._deployment_id = deployment_id
        self._endpoint = endpoint
        self._name = None
        self._sync_status = None
        self._bucket_bandwidth_limit = bucket_bandwidth_limit
        self._bucket_bandwidth_set = bucket_bandwidth_set
        self._bucket_bandwidth_updated_at = None

    @property
    def deployment_id(self):
        """Get deployment ID."""
        return self._deployment_id

    @deployment_id.setter
    def deployment_id(self, value):
        """Set deployment ID."""
        self._deployment_id = value

    @property
    def endpoint(self):
        """Get endpoint."""
        return self._endpoint

    @endpoint.setter
    def endpoint(self, value):
        """Set endpoint."""
        self._endpoint = value

    @property
    def name(self):
        """Get name."""
        return self._name

    @name.setter
    def name(self, value):
        """Set name."""
        self._name = value

    @property
    def sync_status(self):
        """Get sync status."""
        return self._sync_status

    @sync_status.setter
    def sync_status(self, value):
        """Set sync status."""
        self._sync_status = value

    @property
    def bucket_bandwidth_limit(self):
        """Get bucket bandwidth limit."""
        return self._bucket_bandwidth_limit

    @bucket_bandwidth_limit.setter
    def bucket_bandwidth_limit(self, value):
        """Set bucket bandwidth limit."""
        self._bucket_bandwidth_limit = value

    @property
    def bucket_bandwidth_set(self):
        """Get bucket bandwidth set."""
        return self._bucket_bandwidth_set

    @bucket_bandwidth_set.setter
    def bucket_bandwidth_set(self, value):
        """Set bucket bandwidth set."""
        self._bucket_bandwidth_set = value

    @property
    def bucket_bandwidth_updated_at(self):
        """Get bucket bandwidth updated at."""
        return self._bucket_bandwidth_updated_at

    @bucket_bandwidth_updated_at.setter
    def bucket_bandwidth_updated_at(self, value):
        """Set bucket bandwidth updated at."""
        self._bucket_bandwidth_updated_at = value

    def to_dict(self):
        """Converts peer information to dictionary."""
        data = {
            "endpoint": self._endpoint,
            "deploymentID": self._deployment_id,
            "defaultbandwidth": {
                "bandwidthLimitPerBucket": self._bucket_bandwidth_limit,
                "set": self._bucket_bandwidth_set,
            },
        }
        if self._name:
            data["name"] = self._name
        if self._sync_status is not None:
            data["sync"] = "enable" if self._sync_status else "disable"
        if self._bucket_bandwidth_updated_at:
            data["defaultbandwidth"]["updatedAt"] = to_iso8601utc(
                self._bucket_bandwidth_updated_at,
            )
        return data
