import os
import json
import sys
import base64
import posixpath
import requests
from requests.compat import urljoin

def _urljoin(root_url, *url_parts):
    return urljoin(root_url,  "/".join( [str(s) for s in url_parts] ) )

class AssetVCSError(Exception):
    """
    Top level AssetVCS Exception
    """

class AssetVCSPermissionError(AssetVCSError):
    """
    Raise on 403 response
    """

def _wrap_asset_obj(api_object, json_data, asset_location = None, is_full = False):
    return Asset(api_object = api_object, json_data = json_data,asset_location = asset_location,  is_full = is_full)

def _wrap_version_obj(api_object, json_data, version_location = None, is_full = False):
    return Version(api_object = api_object, json_data = json_data, version_location = version_location, is_full = is_full)


def _generate_version_path_num(asset_location, num):
    return posixpath.join(asset_location, "v%03d" % num)


class Version(object):
    def __init__(self, api_object, json_data, version_location = None, is_full = False):
        self._api_object = api_object
        self._json_data = json_data
        self._version_location = version_location
        self._is_full = is_full

    @property
    def asset_id(self):
        return str(self._json_data["asset_id"])

    @property
    def location(self):
        if self._version_location == None:
            raise AssetVCSError("Not found version location")
        return self._version_location

    @property
    def tags(self):
        if self._is_full == False:
            raise  AssetVCSError("You cant access tags from disk, do http query")
        return self._json_data['tags']

    @property
    def num(self):
        return self._json_data["version"]

    def __repr__(self):
        return "VCS Version(asset_id=%s, version = %d)" % (self.asset_id, self.num)

    def update(self):
        """
        Do full http query and modify json_data
        """
        self._json_data = self._api_object.request_GET( ("asset", self.asset_id, "version", self.num) )
        self._is_full = True


    def publish(self, file_list = None):
        if file_list is None:
            raise AssetVCSError("filelist must be sequence of files")

        self._api_object.request_POST(("asset", self.asset_id, "version", self.num, "publish"), dict(file = file_list), expect_output = False)

    def add_tag(self, tag_name):
        if tag_name is None:
            raise AssetVCSError("tag_name must be valid string")
        if tag_name == "latest":
            raise AssetVCSError("'latest' is reserved keyword for tag_name")

        self._api_object.request_POST(("asset", self.asset_id, "version", self.num, "tag"), dict(tag = tag_name), expect_output = False)

    def delete(self):
        self._api_object.request_POST(("asset", self.asset_id, "version", self.num, "delete"), None, expect_output = False)

    def mark_broken(self):
        self._api_object.request_POST(("asset", self.asset_id, "version", self.num, "mark_broken"), None, expect_output = False)

class Asset(object):
    def __init__(self, api_object, json_data, asset_location = None, is_full = False):
        self._api_object = api_object
        self._json_data = json_data
        #if we get construct object from full http query thats true
        self._is_full = is_full
        self._asset_location = asset_location

    @property
    def asset_id(self):
        return str(self._json_data["_id"])

    @property
    def publish_permission(self):
        if self._is_full == False:
            raise  AssetVCSError("You cant access publish permission from disk, do http query")
        return self._json_data['publish_permission']

    @property
    def location(self):
        if self._asset_location == None:
            raise AssetVCSError("Not found asset location")
        return self._asset_location

    def __repr__(self):
        return "VCS Asset(asset_id = %s, location = %s)" % (self.asset_id, self._asset_location)

    def update(self):
        """
        Do full http query and modify json_data
        """
        self._json_data = self._api_object.request_GET( ("asset", self.asset_id) )
        self._is_full = True

    def allocate_version(self):
        json_data = self._api_object.request_POST( ("asset", self.asset_id, "version"), body_data = None  )
        location = _generate_version_path_num(self.location, json_data['version'])
        return _wrap_version_obj ( self._api_object, json_data, location , is_full = True )

    def get_version_http(self, num):
        json_data = self._api_object.request_GET( ("asset", self.asset_id, "version", num) )
        location = _generate_version_path_num(self.location, json_data['version'])
        return _wrap_version_obj( self._api_object,  json_data, location ,is_full = True  )

    def get_latest_version(self):
        """
        we do http query for tag 'latest'
        But PLANNED:
        Special behaviour function
        if we have WIZART_FREELANCE=1 env var
        we return on disk version with highest number avaliable
        else
        we do http query for tag 'latest'
        """
        json_data = self._api_object.request_GET( ("asset", self.asset_id, "version", 'latest') )
        version_path = _generate_version_path_num(self.location, json_data['version'])
        return _wrap_version_obj(self._api_object, json_data,  version_path)

    def get_version(self, num):
        return self.get_version_disk(num)

    def get_version_disk(self, num):
        version_path = _generate_version_path_num(self.location, num)
        if os.path.exists(version_path):
            json_data = {"asset_id": self.asset_id, "version": num }
            return _wrap_version_obj(self._api_object, json_data,  version_path)
        else:
            raise AssetVCSError("Not found version with num '%s'" % num)

    def get_versions(self):
        json_data = self._api_object.request_GET( ("asset", self.asset_id, "version") )
        result = []
        for version in json_data:
            location = _generate_version_path_num(self.location, version['version'])
            result.append( _wrap_version_obj(self._api_object, version, location , is_full = True) )
        return result

class Session(object):
    VCS_ROOT_URL = "http://vcs.burut.net"

    def __init__(self, cookie_jar = None):
        self.root_url = self.VCS_ROOT_URL
        self.user = None
        self._requests_session = requests.Session()
        if cookie_jar:
            self._requests_session.cookies = cookie_jar
    
    def get_request_session(self):
        return self._requests_session

    def is_authenticated(self):
        response = self._requests_session.post(urljoin(self.root_url, 'login'))
        if response.status_code == 200:
            return True
        else:
            return False

    def terminal_login(self):
        login = raw_input("login:")
        import getpass
        password = getpass.getpass()
        self.login(login, password)
        self.user = login

    def login(self, login, password):
        #because we dont use HTTPS in our network
        #basic base64 so we dont see raw password in request debug
        password = base64.b64encode(password)
        self._requests_session.post(urljoin(self.root_url, 'login'),  dict(login = login, password = password) )
        self.user = login
        return True

    def logout(self):
        self._requests_session.post(urljoin(self.root_url, 'logout'))
        self.user = None
        return True

    def request_GET(self, url_parts, expect_output = True):
    
        response = self._requests_session.get(_urljoin(self.root_url, *url_parts))
        if response.status_code != 200:
            if response.status_code == 403:
                raise AssetVCSPermissionError(response.reason)
            else:
                raise AssetVCSError(response.reason)
        
        if expect_output is True:
            return response.json()
        return response

    def request_POST(self, url_parts, body_data, expect_output = True):
        response = self._requests_session.post(_urljoin(self.root_url, *url_parts), body_data)
        if response.status_code != 200:
            if response.status_code == 403:
                raise AssetVCSPermissionError(response.reason)
            else:
                raise AssetVCSError(response.reason)

        if expect_output:
            return response.json()

        return response

    def get_asset_http(self, asset_id):
        return _wrap_asset_obj(self, self.request_GET( ("asset", asset_id) ), is_full = True)

    def get_asset(self, path):
        data = None
        with open( os.path.join(path, "assetvcs-info"), "r") as f:
            data = json.loads(f.read())
        return _wrap_asset_obj(self, data, asset_location = path)

    @staticmethod
    def is_asset(self, path):
        return os.path.exists( os.path.join(path,"assetvcs-info")  )
