# Copyright (c) 2010-2012 OpenStack Foundation
# Copyright (c) 2016-2020 OpenIO SAS
#
# 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.

import json
from xml.etree.cElementTree import Element, SubElement, tostring

from swift.common.utils import public, Timestamp, \
    override_bytes_from_content_type
from swift.common.constraints import check_metadata
from swift.common import constraints
try:
    from swift.common.middleware.listing_formats import \
        get_listing_content_type
except ImportError:
    # Before Queens
    from swift.common.request_helpers import get_listing_content_type

from oioswift.common.middleware.versioned_writes import \
    DELETE_MARKER_CONTENT_TYPE
from swift.common.swob import Response, HTTPBadRequest, HTTPNotFound, \
    HTTPNoContent, HTTPConflict, HTTPPreconditionFailed, HTTPForbidden, \
    HTTPCreated
from swift.common.http import is_success, HTTP_ACCEPTED
from swift.common.request_helpers import is_sys_or_user_meta, get_param
from swift.proxy.controllers.container import ContainerController \
        as SwiftContainerController
from swift.proxy.controllers.base import clear_info_cache, \
    delay_denial, cors_validation, set_info_cache

from oio.common import exceptions

from oioswift.utils import \
    handle_oio_no_such_container, handle_oio_timeout, \
    handle_service_busy, REQID_HEADER, BUCKET_NAME_PROP, MULTIUPLOAD_SUFFIX


class ContainerController(SwiftContainerController):

    pass_through_headers = ['x-container-read', 'x-container-write',
                            'x-container-sync-key', 'x-container-sync-to',
                            'x-versions-location']

    @handle_oio_no_such_container
    @handle_oio_timeout
    @handle_service_busy
    def GETorHEAD(self, req):
        """Handler for HTTP GET/HEAD requests."""
        if self.account_info(self.account_name, req) is None:
            if 'swift.authorize' in req.environ:
                aresp = req.environ['swift.authorize'](req)
                if aresp:
                    return aresp
            return HTTPNotFound(request=req)

        if req.method == 'GET':
            resp = self.get_container_list_resp(req)
        else:
            resp = self.get_container_head_resp(req)
        set_info_cache(self.app, req.environ, self.account_name,
                       self.container_name, resp)
        resp = self.convert_policy(resp)
        if 'swift.authorize' in req.environ:
            req.acl = resp.headers.get('x-container-read')
            aresp = req.environ['swift.authorize'](req)
            if aresp:
                return aresp
        if not req.environ.get('swift_owner', False):
            for key in self.app.swift_owner_headers:
                if key in resp.headers:
                    del resp.headers[key]
        return resp

    def convert_policy(self, resp):
        if 'X-Backend-Storage-Policy-Index' in resp.headers and \
                is_success(resp.status_int):
            policy = self.app.POLICIES.get_by_index(
                resp.headers['X-Backend-Storage-Policy-Index'])
            if policy:
                resp.headers['X-Storage-Policy'] = policy.name
            else:
                self.app.logger.error(
                    'Could not translate %s (%r) from %r to policy',
                    'X-Backend-Storage-Policy-Index',
                    resp.headers['X-Backend-Storage-Policy-Index'])
        return resp

    def get_metadata_resp_headers(self, meta):
        headers = {}
        system = meta.get('system') or {}
        # sys.m2.ctime is microseconds
        ctime = float(system.get('sys.m2.ctime', 0)) / 1000000.0
        headers.update({
            'X-Container-Object-Count': system.get('sys.m2.objects', 0),
            'X-Container-Bytes-Used': system.get('sys.m2.usage', 0),
            'X-Timestamp': Timestamp(ctime).normal,
            # FIXME: save modification time somewhere
            'X-PUT-Timestamp': Timestamp(ctime).normal,
        })
        for (k, v) in meta['properties'].iteritems():
            if v and (k.lower() in self.pass_through_headers or
                      is_sys_or_user_meta('container', k)):
                headers[k] = v
        return headers

    def get_container_list_resp(self, req):
        path = get_param(req, 'path')
        prefix = get_param(req, 'prefix')
        delimiter = get_param(req, 'delimiter')
        if delimiter and (len(delimiter) > 1 or ord(delimiter) > 254):
            # delimiters can be made more flexible later
            return HTTPPreconditionFailed(body='Bad delimiter')
        marker = get_param(req, 'marker', '')
        end_marker = get_param(req, 'end_marker')
        limit = constraints.CONTAINER_LISTING_LIMIT
        given_limit = get_param(req, 'limit')
        if given_limit and given_limit.isdigit():
            limit = int(given_limit)
            if limit > constraints.CONTAINER_LISTING_LIMIT:
                return HTTPPreconditionFailed(
                    request=req,
                    body='Maximum limit is %d'
                         % constraints.CONTAINER_LISTING_LIMIT)

        out_content_type = get_listing_content_type(req)
        if path is not None:
            prefix = path
            if path:
                prefix = path.rstrip('/') + '/'
            delimiter = '/'
        opts = req.environ.get('oio.query', {})
        oio_headers = {REQID_HEADER: self.trans_id}
        oio_cache = req.environ.get('oio.cache')
        perfdata = req.environ.get('oio.perfdata')
        result = self.app.storage.object_list(
            self.account_name, self.container_name, prefix=prefix,
            limit=limit, delimiter=delimiter, marker=marker,
            end_marker=end_marker, properties=True,
            versions=opts.get('versions', False),
            deleted=opts.get('deleted', False),
            force_master=opts.get('force_master', False),
            headers=oio_headers, cache=oio_cache, perfdata=perfdata)

        resp_headers = self.get_metadata_resp_headers(result)
        resp = self.create_listing(
            req, out_content_type, resp_headers, result,
            self.container_name, **opts)
        return resp

    def create_listing(self, req, out_content_type, resp_headers,
                       result, container, **kwargs):
        container_list = result['objects']
        for p in result.get('prefixes', []):
            record = {'name': p,
                      'subdir': True}
            container_list.append(record)
        container_list.sort(key=lambda x: x['name'])
        ret = Response(request=req, headers=resp_headers,
                       content_type=out_content_type, charset='utf-8')
        versions = kwargs.get('versions', False)
        slo = kwargs.get('slo', False)
        if out_content_type == 'application/json':
            ret.body = json.dumps(
                [self.update_data_record(r, versions, slo)