import logging
from random import randint

from deprecated import deprecated
from typing import List, Dict

from tcsoa.backend import RestBackend
from tcsoa.config import TcSoaConfig
from tcsoa.gen.BusinessObjects import BusinessObject
from tcsoa.gen.Server import ServiceData
from tcsoa.gen.Core._2007_12.Session import StateNameValue
from tcsoa.gen.Core._2011_06.Session import Credentials
from tcsoa.gen.Core.services import DataManagementService, SessionService


class TcConnection:
    def __init__(self, url: str, username: str, password: str, descrimator: str = None, insecure: bool = False):
        """
        This class aims to provide a simple interface to create a Teamcenter connection.
        Use it either like so:
            >>> conn = TcConnection('http://my.teamcenter:7001/tc', 'dcproxy', 'dcproxy')
            >>> try:
            >>>     # do TC stuff
            >>> finally:
            >>>     conn.close()
        Or with the Context Manager syntax like this:
            >>> with TcConnection('http://my.teamcenter:7001/tc', 'dcproxy', 'dcproxy'):
            >>>     # do TC stuff
        Or - if you are fearless - just like that
            >>> TcConnection('http://my.teamcenter:7001/tc', 'dcproxy', 'dcproxy')
            >>> # do TC stuff
            >>> TcSoaBasics.logout()


        :param url: Your Teamcenter URL, like 'http://my.teamcenter:7001/tc'
        :param username: The Teamcenter username to be used to connect to Teamcenter
        :param password: The Teamcenter password of the username provided to connect to Teamcenter
        :param descrimator: The descrimator to use - if not provided, a random descriminator will be used
        :param insecure: A flag for convenience to disable TLS checking
        """
        self.url = url
        self.username = username
        self.password = password
        self.descrimator = descrimator
        self.insecure = insecure

        self.connect()

    def __enter__(self):
        pass

    def connect(self):
        TcSoaConfig.backend = RestBackend(self.url)
        if self.insecure:
            TcSoaConfig.backend.session.verify = False
            import urllib3
            urllib3.disable_warnings()

        TcSoaBasics.setCredentials(self.username, self.password)
        TcSoaBasics.descrimator = self.descrimator if self.descrimator else f'TcSoa_{randint(0, 999999)}'
        login_result = TcSoaBasics.login()
        login_success = len(login_result['PartialErrors']) == 0
        if login_success:
            logging.info(f'Logged in on Server {TcSoaBasics.server} - with log file {TcSoaBasics.log_file}')
            return self
        else:
            errors = '\n'.join(login_result['PartialErrors'])
            logging.error('Error occured when logging in:\n' + errors)
            raise ConnectionError(errors)

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    def close(self):
        TcSoaBasics.logout()


def asd():
    with TcConnection('http://my.teamcenter:7001/tc', 'dcproxy', 'dcproxy') as f:
        pass


class TcSoaBasics:
    NULLTAG = 'AAAAAAAAAAAAAA'

    # Login configuration
    username = None
    password = None
    group = ''
    role = ''
    locale = 'en_US'
    descrimator = 'TcSoa.py'

    # Server Information
    log_file: str = None
    server: str = None

    @classmethod
    def setCredentials(cls, username, password, group='', role=''):
        cls.username = username
        cls.password = password
        cls.group = group
        cls.role = role

    @classmethod
    def login(cls):
        login_response = SessionService().login3(
            credentials=Credentials(
                user=cls.username,
                password=cls.password,
                group=cls.group,
                role=cls.role,
                locale=cls.locale,
                descrimator=cls.descrimator,
            )
        )
        cls.log_file = login_response.serverInfo['LogFile']
        cls.server = login_response.serverInfo['HostName']
        return login_response

    @classmethod
    def logout(cls):
        return SessionService().logout()

    @classmethod
    def load_objects(cls, uids: List[str]) -> Dict[str, BusinessObject]:
        if not uids:
            return dict()
        load_objs_result = DataManagementService().loadObjects(uids=uids)
        model_objects = load_objs_result['modelObjects']
        return {p: model_objects[p] for p in load_objs_result['plain']}

    @classmethod
    def load_object(cls, uid: str):
        obj = None
        for obj in cls.load_objects([uid]).values():
            break
        return obj

    @classmethod
    def getProperties(cls, objects: List[BusinessObject], properties: List[str]) -> Dict[str, BusinessObject]:
        response = DataManagementService().getProperties(
            objects=objects,
            attributes=properties
        )
        return {uid: response['modelObjects'][uid] for uid in response['plain']}

    @classmethod
    @deprecated(version="0.5.0", reason="Use the other getPropXXXX methods instead.")
    def getProperty(cls, bo: BusinessObject, prop_name: str):
        return cls.getProperties([bo], [prop_name])[bo['uid']]['props'][prop_name]

    @classmethod
    def getPropString(cls, bo: BusinessObject, prop_name: str):
        return cls.getPropStringList(bo, prop_name)[0]

    @classmethod
    def getPropStringList(cls, bo: BusinessObject, prop_name: str):
        return cls.getProperties([bo], [prop_name])[bo['uid']]['props'][prop_name]['uiValues']

    @classmethod
    def getMultiPropStringList(cls, bos: List[BusinessObject], prop_name: str):
        return {uid: obj.prop(prop_name) for uid, obj in cls.getProperties(bos, [prop_name]).items()}

    @classmethod
    def getMultiPropString(cls, bos: List[BusinessObject], prop_name: str):
        return {uid: val_list[0] for uid, val_list in cls.getMultiPropStringList(bos, prop_name).items()}

    @classmethod
    def getPropObject(cls, bo: BusinessObject, prop_name: str):
        return cls.getPropObjectList(bo, prop_name)[0]

    @classmethod
    def getPropObjectList(cls, bo: BusinessObject, prop_name: str):
        response: ServiceData = DataManagementService().getProperties(
            objects=[bo],
            attributes=[prop_name],
        )
        ref_obj_uids = response.modelObjects[bo.uid].prop(prop_name, db_value=True)
        return [response.modelObjects[uid] for uid in ref_obj_uids]

    @classmethod
    def getMultiPropObjectList(cls, bos: List[BusinessObject], prop_name: str):
        response = DataManagementService().getProperties(
            objects=bos,
            attributes=[prop_name],
        )
        obj2refs_map = {bo.uid: response.modelObjects[bo.uid].prop(prop_name, db_value=True) for bo in bos}
        return {uid: [response.modelObjects[ref] for ref in refs] for uid, refs in obj2refs_map.items()}

    @classmethod
    def getMultiPropObject(cls, bos: List[BusinessObject], prop_name: str):
        return {uid: val_list[0] for uid, val_list in cls.getMultiPropObjectList(bos, prop_name).items()}

    @classmethod
    def bool2property_str(cls, val: bool) -> str:
        return '1' if val else '0'

    @classmethod
    def setBypass(cls, enabled=True):
        return SessionService().setUserSessionState(
            pairs=[
                StateNameValue(
                    name='fnd0bypassflag',
                    value=cls.bool2property_str(enabled)
                )
            ]
        )

    @classmethod
    def set_object_load_policy(cls, bo_name: str, properties: List[str]):
        rest_backend: RestBackend = TcSoaConfig.backend
        rest_backend.set_object_load_policy(bo_name, properties)
