import requests
import shutil
import datetime
import os
import io
import json
import sys
import time
import pandas as pd
import cson

class Toucan:
    """Toucan lib"""
    # Private vars
    __url_screenshot_api = os.environ.get(
        'TC_API_SCREENSHOT', 'http://toucan-screenshot:3000/screenshot')
    __url_config = 'config'
    __url_load = 'load'
    __url_release = 'release'
    __url_reports = 'reports'
    __url_data = 'data'
    __url_login = 'login'
    __ur_ids = 'ids'
    __url_users = 'users'
    __url_small_apps = 'small-apps'
    __url_tc_params = 'scripts/tc-params.js'
    __url_tc_app_version = 'tucana-version.txt'
    __replace_tc_params = 'window.TC_PARAMS = '
    __today = datetime.date.today().strftime("%Y_%m_%d")
    __token = None
    __url_name = None
    # Public vars
    urlBase = None
    url_api = None
    login = {}
    user = {}
    config = {}
    tc_params = {}
    small_apps = []

    def __init__(self, url, login, mode='prod'):
        self.urlBase = url
        host = url.partition('://')[2]
        self.__url_name = host.partition('.')[0]
        self.login = login
        self.mode = mode
        try:
            if self.mode == 'debug':
                print("Get toucan tc-params")
            self.tc_params = self.__request_tc_params()
            if self.mode == 'debug':
                print("Get toucan url_api")
            self.url_api = self.tc_params.get('API_BASEROUTE')
            if self.mode == 'debug':
                print("Get toucan user")
            self.user = self.__request_user()
            if self.mode == 'debug':
                print("Get toucan token")
            self.__token = self.user.get('token')
            if self.mode == 'debug':
                print("Get toucan small_apps")
            self.small_apps = self.__request_small_apps()
        except requests.exceptions.HTTPError as errh:
            print("Http Error:", errh.response.text)
        except requests.exceptions.ConnectionError as errc:
            print("Error Connecting:", errc)
        except requests.exceptions.Timeout as errt:
            print("Timeout Error:", errt)
        except requests.exceptions.RequestException as err:
            print("OOps: Something Else", err)
        except:
            print("Unexpected error:", sys.exc_info()[0])
            print('user cannot be logged')
            return
        if self.mode == 'debug':
            print('user logged')

    def get_headers(self):
        return {"authorization": f"Bearer {self.__token}"}

    def __request_small_apps(self):
        req = requests.get(f"{self.url_api}/{self.__url_small_apps}",
                           headers=self.get_headers())
        req.raise_for_status()
        return req.json()

    def __request_tc_params(self):
        req = requests.get(f"{self.urlBase}/{self.__url_tc_params}")
        req.raise_for_status()
        res = req.text.replace(self.__replace_tc_params, '').replace(
            '\t', ' ').replace('\n', '').replace(';', '')
        jsonConf = json.loads(res)
        return jsonConf

    def __request_user(self):
        req = requests.post(
            f"{self.url_api}/{self.__url_login}", json=self.login)
        req.raise_for_status()
        return req.json()

    def __createFolder(self, name):
        try:
            os.makedirs(name)
            print(f"Successfully created the directory {name}")
        except OSError:
            print(f"Directory {name} already exist")

    def dashboardSelector(self):
        statusApp = self.get_version()
        version = statusApp['frontVersion']
        if (version == 'v64.0.0'):
            return '.dashboard-report'
        else:
            return '.small-app-home__content'

    def __generateUrls(self, app_name, config, reports, ids):
        arr = []
        if self.mode == 'debug':
            print("Get app slides")
        slides = config.get('slides')
        if self.mode == 'debug':
            print("Get app Home")
        home = config.get('home')
        if home is not None and "skipToReport" in home:
            arr.append({'url': f"{self.urlBase}/{app_name}",
                        'selector': self.dashboardSelector(), "name": "dashboard"})
            if self.mode == 'debug':
                print("Add New dashboard to url list")
        else:
            arr.append({'url': f"{self.urlBase}/{app_name}",
                        'selector': '.report-execsum', "name": "dashboard"})
            if self.mode == 'debug':
                print("Add Old dashboard to url list")
        if self.mode == 'debug':
            print("Generate all url from config")
        for report in reports:
            if 'id' in report and 'entityName' in report:
                for slide in slides:
                    if 'parent_id' in slide and 'id' in slide and 'title' in slide:
                        name = self.generateScreenShotName(slide, report)
                        rId = self.__calcReportId(report.get('id'), ids)
                        if self.mode == 'debug':
                            print("Generate url for", name)
                        arr.append({'url': f"{self.urlBase}/{app_name}?report={rId}&dashboard={rId}&slide={slide.get('id')}",
                                    'selector': '.tc-slide__content, .tc-story', "name": name})
        return arr

    def get_version(self):
        reqApi = requests.get(f"{self.url_api}",
                              headers=self.get_headers())
        reqApi.raise_for_status()
        result = reqApi.json()
        reqApp = requests.get(f"{self.urlBase}/{self.__url_tc_app_version}")
        reqApp.raise_for_status()
        frontVersion = reqApp.text().strip()
        result['frontVersion'] = frontVersion
        return result

    def get_app_config(self, app_name):
        req = requests.get(f"{self.url_api}/{app_name}/{self.__url_config}",
                           headers=self.get_headers())
        req.raise_for_status()
        return req.json()

    def get_app_data(self, app_name):
        req = requests.get(f"{self.url_api}/{app_name}/{self.__url_data}",
                           headers=self.get_headers())
        req.raise_for_status()
        return req.json()

    def get_app_reportsIds(self, app_name):
        req = requests.get(f"{self.url_api}/{app_name}/{self.__url_reports}/{self.__ur_ids}",
                           headers=self.get_headers())
        req.raise_for_status()
        return req.json()

    def get_app_reports(self, app_name):
        req = requests.get(f"{self.url_api}/{app_name}/{self.__url_reports}",
                           headers=self.get_headers())
        req.raise_for_status()
        return req.json()

    def get_users(self):
        req = requests.get(f"{self.url_api}/{self.__url_users}",
                        headers=self.get_headers())
        req.raise_for_status()
        return req.json()

    def get_users_analytics(self):
        users = self.get_users()
        flatten_users = []
        for user in users:
            if user.get("logins"):
                for login in user.get("logins"):
                    date = time.ctime(login.get("date").get("$date")/1000)
                    TIME = datetime.datetime.strptime(date, "%a %b %d %H:%M:%S %Y").strftime("%H:%M:%S")
                    DATE = datetime.datetime.strptime(date, "%a %b %d %H:%M:%S %Y").strftime("%Y-%m-%d")
                    flatten = {"USER": user.get("username"), "TIME": TIME, "DATE": DATE}
                    flatten_users.append(flatten)
            else:
                flatten = {"USER": user.get("username"),"TIME": None, "DATE": None}
                flatten_users.append(flatten)

        df = pd.DataFrame(flatten_users).drop(["TIME"], axis = 1).drop_duplicates().reset_index(drop = True)
        df["VALUE"] = 1

        df_session = df.copy()
        df_session = df_session.groupby(["USER", "DATE"], as_index = False).count()

        df_session_all = df.copy()
        df_session_all = df_session_all.drop(["USER"], axis = 1).groupby(["DATE"], as_index = False).count()
        df_session_all["USER"] = "All users"

        df_final = pd.concat([df_session, df_session_all], axis = 0)
        return df_final

    def send_app_config(self, app_name, file_upload, format_file='front_config', stage='staging'):
        config_name = ''
        formatFile = '&format=cson'
        files = None
        data = None
        if (format_file == "front_config"):
            formatFile = '&format=cson'
            files = {'file': io.StringIO(json.dumps({"json": cson.load(file_upload)})) }
        elif (format_file == "etl_config"):
            config_name = '/etl'
            formatFile = '&format=cson'
            data = file_upload
        elif (format_file == "report"):
            config_name = '/report'
            formatFile = '&format=cson'
            data = file_upload
        elif (format_file == "dashboard-Group"):
            config_name = '/dashboard-Group'
            formatFile = '&format=cson'
            data = file_upload
        elif (format_file == "augment.py"):
            config_name = '/augment'
            files = {'file': file_upload}
            formatFile = ''
        elif (format_file == "preprocess_validation"):
            config_name = '/preprocess_validation'
            formatFile = '&format=cson'
            data = file_upload
        elif (format_file == "permissions.py"):
            config_name = '/permissions'
            files = {'file': file_upload}
            formatFile = ''
        elif (format_file == "permissions_config"):
            config_name = '/permissions_config'
            formatFile = '&format=cson'
            data = file_upload
        elif (format_file == "notifications_handlers.py"):
            config_name = '/notifications_handlers'
            files = {'file': file_upload}
            formatFile = ''

        req = requests.put(f"{self.url_api}/{app_name}/{self.__url_config}{config_name}?stage={stage}{formatFile}",
                           headers=self.get_headers(), files=files, data=data)
        req.raise_for_status()
        return req.json()

    def download_app_config(self, app_name, format_file='front_config', stage='staging'):
        config_name = ''
        format_file = '&format=cson'
        if (format_file == "etl_config"):
            config_name = '/etl'
        elif (format_file == "report"):
            config_name = '/report'
        elif (format_file == "dashboard-Group"):
            config_name = '/dashboard-Group'
        elif (format_file == "augment.py"):
            config_name = '/augment'
            formatFile = ''
        elif (format_file == "preprocess_validation"):
            config_name = '/preprocess_validation'
        elif (format_file == "permissions.py"):
            config_name = '/permissions'
            formatFile = ''
        elif (format_file == "permissions_config"):
            config_name = '/permissions_config'
        elif (format_file == "notifications_handlers.py"):
            config_name = '/notifications_handlers'
            formatFile = ''

        req = requests.get(f"{self.url_api}/{app_name}/{self.__url_config}{config_name}?stage={stage}{formatFile}",
                           headers=self.get_headers())
        req.raise_for_status()
        return req

    def deploy_app(self, app_name, stage='staging', operations=["preprocess_data_sources", "populate_reports", "populate_dashboards", "release_design"], force=True):
        force_str = "true" if force else "false"
        req = requests.post(f"{self.url_api}/{app_name}/{self.__url_config}/pull?force={force_str}&stage={stage}",
                            headers=self.get_headers(), json={"operations": operations})
        req.raise_for_status()
        return req.json()

    def get_data(self, app_name, domain, stage='staging'):
        req = requests.get(f"{self.url_api}/{app_name}/domain/{domain}?stage={stage}",
                            headers=self.get_headers())
        req.raise_for_status()
        s_data = str(req.content,'utf-8')
        return io.StringIO(s_data)

    def get_metadata(self, app_name, stage='staging'):
        req = requests.get(f"{self.url_api}/{app_name}/metadata?stage={stage}", 
                            headers=self.get_headers())
        req.raise_for_status()
        return req.json()

    def create_small_app(self, app_name, id_app=None):
        body = {
            "name": app_name,
            "id": id_app if id_app else app_name
        }
        req = requests.post(f"{self.url_api}/small-apps", headers=self.get_headers(), json=body)
        req.raise_for_status()
        return req.json()

    def load_operations(self, app_name, stage='staging', notification=False, operations=["preprocess_data_sources", "populate_basemaps", "populate_reports", "populate_dashboards", "populate_permissions"]):
        notificationStr = "true" if notification else "false"
        req = requests.post(f"{self.url_api}/{app_name}/{self.__url_config}/operations?notify={notificationStr}&stage={stage}",
                            headers=self.get_headers(), json={"operations": operations})
        req.raise_for_status()
        return req.json()

    def load_conf(self, app_name, stage='staging'):
        req = requests.post(f"{self.url_api}/{app_name}/{self.__url_load}?stage={stage}",
                            headers=self.get_headers())
        req.raise_for_status()
        return req.json()

    def release_conf(self, app_name, stage='staging'):
        req = requests.post(f"{self.url_api}/{app_name}/{self.__url_release}?stage={stage}",
                            headers=self.get_headers())
        req.raise_for_status()
        return req.json()

    def screenshotsAppAll(self):
        for app in self.small_apps:
            self.screenshotsApp(app)

    def getReportByName(self, reports, reportName):
        reportFound = None
        if (not reports or len(reports) == 0):
            return reportFound
        for report in reports:
            if reportName is not None and 'id' in report and 'entityName' in report and report.get('entityName') == reportName:
                reportFound = report
        return reportFound

    def generateScreenShotName(self, slide, report):
        title = slide.get('title', 'noTitle')
        title = "".join(x for x in title if x.isalnum())
        name = report.get('entityName', 'noEntity')
        name = "".join(x for x in name if x.isalnum())
        return f"{name}_{title}"

    def __calcReportId(self, currentId, ids):
        calcId = currentId
        if ids is not None and 0 in ids:
            calcId += int(ids.get(0))
        return calcId

    def generateFolderName(self, app_name):
        return f"{self.__today}/{self.__url_name}/{app_name}"

    def isAppAllowed(self, app_name):
        res = False
        for small_app in self.small_apps:
            if (small_app.get('id') == app_name):
                res = True
        return res

    def __filterReport(self, reports, reportName):
        resReport = [{'id': 0, 'entityName': 'default'}]
        if (reportName is None):
            return reports
        if (not reports or len(reports) == 0):
            return resReport
        found = self.getReportByName(reports, reportName)
        if (found):
            resReport = [found]
        return resReport

    def screenshotsApp(self, app_name, reportName=None, fullSize=False):
        if self.mode == 'debug':
            print("Generate screenshots for", app_name)
        if self.isAppAllowed(app_name):
            folderName = self.generateFolderName(app_name)
            self.__createFolder(folderName)
            if self.mode == 'debug':
                print("Get app config")
            config = self.get_app_config(app_name)
            ids = None
            try:
                ids = self.get_app_reportsIds(app_name)
            except:
                print("Cannot get report ids")
            reportsAll = self.get_app_reports(app_name)
            reports = self.__filterReport(reportsAll, reportName)
            urls = self.__generateUrls(app_name, config, reports, ids)
            for url in urls:
                req = ''
                path = f"{folderName}/{url.get('name')}.png"
                try:
                    if self.mode == 'debug':
                        print(f"Request Screenshot {path}")
                    req = requests.get(self.__url_screenshot_api, params={
                        'url': url.get('url'),
                        'format': 'png',
                        'lang': 'en',
                        'fullSize': fullSize,
                        'deviceScaleFactor': 1,
                        'token': self.__token,
                        'elementSelector': url.get('selector')
                    }, stream=True)
                    req.raise_for_status()
                except requests.exceptions.HTTPError as errh:
                    print("Http Error:", errh.response.text)
                except requests.exceptions.ConnectionError as errc:
                    print("Error Connecting:", errc)
                except requests.exceptions.Timeout as errt:
                    print("Timeout Error:", errt)
                except requests.exceptions.RequestException as err:
                    print("OOps: Something Else", err)
                path = f"{folderName}/{url.get('name')}.png"
                if req.status_code == 200:
                    with open(path, 'wb') as out_file:
                        shutil.copyfileobj(req.raw, out_file)
                        if self.mode == 'debug':
                            print(f"Created Screenshot {path}")
        else:
            print('this app is not allow to your account')
