# -*- coding: utf-8 -*-

# Copyright (C) 2010-2018 RhodeCode GmbH
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/

import logging
import urllib
from urlparse import urljoin


import requests
from webob.exc import HTTPNotAcceptable

from rhodecode.lib.middleware import simplevcs
from rhodecode.lib.utils import is_valid_repo
from rhodecode.lib.utils2 import str2bool

log = logging.getLogger(__name__)


class SimpleSvnApp(object):
    IGNORED_HEADERS = [
        'connection', 'keep-alive', 'content-encoding',
        'transfer-encoding', 'content-length']
    rc_extras = {}


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

    def __call__(self, environ, start_response):
        request_headers = self._get_request_headers(environ)

        data = environ['wsgi.input']
        # johbo: Avoid that we end up with sending the request in chunked
        # transfer encoding (mainly on Gunicorn). If we know the content
        # length, then we should transfer the payload in one request.
        if environ['REQUEST_METHOD'] == 'MKCOL' or 'CONTENT_LENGTH' in environ:
            data = data.read()

        log.debug('Calling: %s method via `%s`', environ['REQUEST_METHOD'],
                  self._get_url(environ['PATH_INFO']))
        response = requests.request(
            environ['REQUEST_METHOD'], self._get_url(environ['PATH_INFO']),
            data=data, headers=request_headers)

        if response.status_code not in [200, 401]:
            if response.status_code >= 500:
                log.error('Got SVN response:%s with text:`%s`',
                          response, response.text)
            else:
                log.debug('Got SVN response:%s with text:`%s`',
                          response, response.text)
        else:
            log.debug('got response code: %s', response.status_code)

        response_headers = self._get_response_headers(response.headers)
        start_response(
            '{} {}'.format(response.status_code, response.reason),
            response_headers)
        return response.iter_content(chunk_size=1024)

    def _get_url(self, path):
        url_path = urljoin(
            self.config.get('subversion_http_server_url', ''), path)
        url_path = urllib.quote(url_path, safe="/:=~+!$,;'")
        return url_path

    def _get_request_headers(self, environ):
        headers = {}

        for key in environ:
            if not key.startswith('HTTP_'):
                continue
            new_key = key.split('_')
            new_key = [k.capitalize() for k in new_key[1:]]
            new_key = '-'.join(new_key)
            headers[new_key] = environ[key]

        if 'CONTENT_TYPE' in environ:
            headers['Content-Type'] = environ['CONTENT_TYPE']

        if 'CONTENT_LENGTH' in environ:
            headers['Content-Length'] = environ['CONTENT_LENGTH']

        return headers

    def _get_response_headers(self, headers):
        headers = [
            (h, headers[h])
            for h in headers
            if h.lower() not in self.IGNORED_HEADERS
        ]

        return headers


class DisabledSimpleSvnApp(object):
    def __init__(self, config):
        self.config = config

    def __call__(self, environ, start_response):
        reason = 'Cannot handle SVN call because: SVN HTTP Proxy is not enabled'
        log.warning(reason)
        return HTTPNotAcceptable(reason)(environ, start_response)


class SimpleSvn(simplevcs.SimpleVCS):

    SCM = 'svn'
    READ_ONLY_COMMANDS = ('OPTIONS', 'PROPFIND', 'GET', 'REPORT')
    DEFAULT_HTTP_SERVER = 'http://localhost:8090'

    def _get_repository_name(self, environ):
        """
        Gets repository name out of PATH_INFO header

        :param environ: environ where PATH_INFO is stored
        """
        path = environ['PATH_INFO'].split('!')
        repo_name = path[0].strip('/')

        # SVN includes the whole path in it's requests, including
        # subdirectories inside the repo. Therefore we have to search for
        # the repo root directory.
        if not is_valid_repo(
                repo_name, self.base_path, explicit_scm=self.SCM):
            current_path = ''
            for component in repo_name.split('/'):
                current_path += component
                if is_valid_repo(
                        current_path, self.base_path, explicit_scm=self.SCM):
                    return current_path
                current_path += '/'

        return repo_name

    def _get_action(self, environ):
        return (
            'pull'
            if environ['REQUEST_METHOD'] in self.READ_ONLY_COMMANDS
            else 'push')

    def _create_wsgi_app(self, repo_path, repo_name, config):
        if self._is_svn_enabled():
            return SimpleSvnApp(config)
        # we don't have http proxy enabled return dummy request handler
        return DisabledSimpleSvnApp(config)

    def _is_svn_enabled(self):
        conf = self.repo_vcs_config
        return str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))

    def _create_config(self, extras, repo_name):
        conf = self.repo_vcs_config
        server_url = conf.get('vcs_svn_proxy', 'http_server_url')
        server_url = server_url or self.DEFAULT_HTTP_SERVER

        extras['subversion_http_server_url'] = server_url
        return extras