from datetime import datetime
import hashlib
import json
import os
import requests
from requests.auth import HTTPBasicAuth
from requests_oauthlib import OAuth1
from urllib.parse import urljoin, parse_qs

import fylr_lib_plugin_python3.util as fylr_util


def __tmp(lines: list, newfile: bool = False):
    # useful for fylr only, easydb5 should use logger

    # uncomment to not write temporary files for this plugin
    return

    if not isinstance(lines, list):
        lines = [lines]
    if len(lines) < 1:
        return

    fylr_util.write_tmp_file(
        name='wordpress-plugin.json',
        lines=[str(l) for l in lines],
        new_file=newfile,
    )


def raise_error(err):
    dbg = 'Wordpress error: {0}'.format(err)
    __tmp(dbg)
    raise Exception(dbg)


def get_event_name(is_scheduled: bool):
    if is_scheduled:
        return 'ASSET_EXPORT_TRANSPORT_COPY_SCHEDULED'
    return 'ASSET_EXPORT_TRANSPORT_COPY'


def build_wp_media_url(url: str) -> str:
    return urljoin(url, 'wp-json/wp/v2/media')


def build_req_auth(auth: dict):
    if auth['auth_type'] == 'http-auth':
        return HTTPBasicAuth(auth['http_auth_login'], auth['http_auth_password'])
    elif auth['auth_type'] == 'oauth-1':
        return OAuth1(
            auth['oauth_client_key'],
            auth['oauth_client_secret'],
            auth['oauth_token'],
            auth['oauth_token_secret']
        )
    return None


def __error_response(code: str, msg: str) -> dict:
    msg = fylr_util.dumpjs({
        'error': {
            'realm': 'user',
            'code': code,
            'parameters': {
                'error': msg,
            }
        }
    })
    return {
        'headers': {
            'Content-Type': 'text/html'
        },
        'body': """
            <html><head><script>
                window.opener.postMessage(""" + msg + """,'*');
            </script></head></html>
        """,
    }


def oauth1(query_params: dict, plugin_url: str, api_callback_token: str = None, logger=None):

    oauth_callback = urljoin(plugin_url, 'oauth1')

    # this is the callback from WP, we parse this and call ourselves once
    # again for the final step
    if query_params.get('oauth_verifier'):
        oauth_verifier = query_params.get('oauth_verifier')[0]

        dbg = 'api callback: oauth1 | oauth_verifier={0}'.format(oauth_verifier)
        if logger is not None:
            logger.debug(dbg)
        else:
            __tmp(dbg)

        return {
            'headers': {
                'Content-Type': 'text/html'
            },
            'body': """
                <html><head><script>
                    var wp_oauth = JSON.parse(window.sessionStorage.getItem("wp_oauth"));
                    wp_oauth.verifier = '""" + oauth_verifier + """';
                    var params = [];
                    for (k in wp_oauth) {
                        params.push(encodeURIComponent(k)+'='+encodeURIComponent(wp_oauth[k]));
                    }
                    """
                    + (("""params.push('access_token=""" + api_callback_token + """');""") if api_callback_token is not None else '')
                    + """
                    url = '""" + oauth_callback + """?' + params.join('&');
                    document.location.assign(url);
                </script></head></html>
            """,
        }

    if 'wp_url' not in query_params:
        return __error_response(
            'wordpress.oauth_error',
            'Parameter wp_url missing.'
        )

    wp_url = query_params.get('wp_url')[0]

    dbg = 'api callback: oauth1 | wp_url={0}'.format(wp_url)
    if logger is not None:
        logger.debug(dbg)
    else:
        __tmp(dbg)

    r = requests.get(urljoin(wp_url, 'wp-json'))
    if r.status_code != 200:
        dbg = 'api callback: oauth1 | response from {0}: {1} - {2}'.format(
            urljoin(wp_url, 'wp-json'),
            r.status_code,
            r.content,
        )
        if logger is not None:
            logger.debug(dbg)
        else:
            __tmp(dbg)

    try:
        authentication = json.loads(r.content).get('authentication')
        wp_urls = authentication.get('oauth1')
    except:
        return __error_response(
            'wordpress.oauth_error',
            'Authentication \'oauth1\' not allowed.'
        )

    dbg = 'api callback: oauth1 | response from {0}: authentication={1}'.format(
        urljoin(wp_url, 'wp-json'),
        fylr_util.dumpjs(authentication),
    )
    if logger is not None:
        logger.debug(dbg)
    else:
        __tmp(dbg)

    # this is the callback from ourselves after we got the callback from WP
    if query_params.get('verifier'):

        dbg = 'api callback: oauth1 | verifier={0}'.format(query_params.get('verifier'))
        if logger is not None:
            logger.debug(dbg)
        else:
            __tmp(dbg)

        wp_oauth = {}
        for key in [
            'wp_url',
            'client_key',
            'client_secret',
            'resource_owner_key',
            'resource_owner_secret',
            'verifier'
        ]:
            wp_oauth[key] = query_params[key][0]

        oauth = OAuth1(
            wp_oauth['client_key'],
            client_secret=wp_oauth['client_secret'],
            resource_owner_key=wp_oauth['resource_owner_key'],
            resource_owner_secret=wp_oauth['resource_owner_secret'],
            verifier=wp_oauth['verifier'])

        r = requests.post(url=wp_urls['access'], auth=oauth)

        credentials = parse_qs(r.content.decode('utf-8'))

        resource_owner_key = credentials.get('oauth_token')[0]
        resource_owner_secret = credentials.get('oauth_token_secret')[0]

        dbg = 'api callback: oauth1 | resource_owner_key={0}'.format(resource_owner_key)
        if logger is not None:
            logger.debug(dbg)
        else:
            __tmp(dbg)
        dbg = 'api callback: oauth1 | resource_owner_secret={0}'.format(resource_owner_secret)
        if logger is not None:
            logger.debug(dbg)
        else:
            __tmp(dbg)

        msg = fylr_util.dumpjs({
            "oauth_token": credentials.get('oauth_token')[0],
            "oauth_token_secret": credentials.get('oauth_token_secret')[0],
        }, indent=None)

        return {
            'headers': {
                'Content-Type': 'text/html'
            },
            'body': """
                <html><head><script>
                    window.opener.postMessage(""" + msg + """,'*');
                </script></head></html>""",
        }

    # initial request coming from easydb
    if query_params.get('client_key'):

        client_key = query_params.get('client_key')[0]
        client_secret = query_params.get('client_secret')[0]

        dbg = 'api callback: oauth1 | client_key={0}'.format(client_key)
        if logger is not None:
            logger.debug(dbg)
        else:
            __tmp(dbg)
        dbg = 'api callback: oauth1 | oauth_callback={0}'.format(oauth_callback)
        if logger is not None:
            logger.debug(dbg)
        else:
            __tmp(dbg)

        oauth = OAuth1(client_key, client_secret=client_secret)
        r = requests.post(
            url=wp_urls['request'],
            params={
                'oauth_callback': oauth_callback + ('?access_token=' + api_callback_token if api_callback_token is not None else '')
            },
            auth=oauth)

        if r.status_code != 200:
            return __error_response(
                'wordpress.oauth_error',
                r.content.decode('utf-8', 'replace')
            )

        credentials = parse_qs(r.content.decode('utf-8'))
        resource_owner_key = credentials.get('oauth_token')[0]
        resource_owner_secret = credentials.get('oauth_token_secret')[0]

        wp_oauth = json.dumps({
            'wp_url': wp_url,
            'client_key': client_key,
            'client_secret': client_secret,
            'resource_owner_key': resource_owner_key,
            'resource_owner_secret': resource_owner_secret
        })

        authorize_url = '{0}?oauth_token={1}'.format(wp_urls.get('authorize'), resource_owner_key)
        dbg = 'api callback: oauth1 | resource_owner_key={0}'.format(resource_owner_key)
        if logger is not None:
            logger.debug(dbg)
        else:
            __tmp(dbg)
        dbg = 'api callback: oauth1 | => authorize_url={0}'.format(authorize_url)
        if logger is not None:
            logger.debug(dbg)
        else:
            __tmp(dbg)

        # redirect browser to wordpress
        # wordpress will redirect back to us

        return {
            'headers': {
                'Content-Type': 'text/html'
            },
            'body': """
                <html><head><script>
                    window.sessionStorage.setItem("wp_oauth", '""" + wp_oauth + """');
                    document.location.assign('""" + authorize_url + """');
                </script></head></html>
            """
        }

    return __error_response(
        'wordpress.oauth_error',
        'Required parameter missing'
    )


def __download_export_file(url, f_path):
    __tmp('download export file {0} from {1}'.format(f_path, url))
    resp = requests.get(url)

    if resp.status_code == 200:
        base_dir = os.path.dirname(f_path)
        if not os.path.exists(base_dir):
            os.makedirs(base_dir)

        with open(os.path.abspath(f_path), 'wb') as outf:
            outf.write(resp.content)
        return

    dbg = 'could not get file from fylr: status code {0}: {1}'.format(resp.status_code, resp.text)
    __tmp(dbg)
    raise_error(dbg)


def transport_files(
    wp_media_url: str,
    fpath: str,
    files: list,
    auth: dict,
    user_generated_displayname: str,
    source_name: str,  # 'easydb' or 'fylr'
    export_id: int = None,  # only needed for fylr
    api_callback_url: str = None,  # only needed for fylr
    api_callback_token: str = None  # only needed for fylr
) -> list:

    events = []
    protocol = []

    req_auth = build_req_auth(auth)
    if req_auth is None:
        dbg = 'could not form request authentication'
        protocol.append(dbg)
        __tmp(dbg)
        return [], protocol

    for fo in files:
        path = os.path.join(fpath, fo['path'])
        filename = os.path.basename(path)
        needs_file_download = False

        fileclass = fylr_util.get_json_value(fo, 'eas_fileclass')  # easydb5
        if not isinstance(fileclass, str):
            fileclass = fylr_util.get_json_value(fo, 'export_file_internal.content_type')  # fylr
            if isinstance(fileclass, str):
                fileclass = fileclass.split('/')[0]
                needs_file_download = True

        if fileclass != 'image':
            dbg = 'SKIPPED: \'{0}\', wrong fileclass'.format(filename)
            protocol.append(dbg)
            __tmp(dbg)
            continue

        if needs_file_download:
            # easydb5 exports have already saved the file on disk
            # fylr exports are done on request, so files need to be loaded
            __download_export_file(
                '{0}/api/v1/export/{1}/file/{2}?access_token={3}'.format(
                    api_callback_url,
                    export_id,
                    path,
                    api_callback_token,
                ),
                path
            )

        headers = {
            'Content-Type': 'application/octet-stream',
            'Content-Disposition': 'attachment; filename="{0}"'.format(filename),
        }

        title = filename
        description = 'Imported from {0}: {1}. User: {2}.'.format(
            source_name,
            datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            user_generated_displayname
        )

        # produce a unique id for the file we are pushing

        with open(path, 'rb') as f:
            slug = hashlib.md5(f.read()).hexdigest()

        # let's see if the file exists in WP already
        r = requests.get(
            wp_media_url,
            params={
                'slug': slug,
                'title': title,
                'description': description
            },
            auth=req_auth
        )

        response = json.loads(r.content)

        if not (200 <= r.status_code <= 299):
            return raise_error(response.get('message').encode('utf-8'))

        files_by_slug = json.loads(r.content)

        if len(files_by_slug) == 0:
            # file is new, push it
            with open(path, 'rb') as f:
                r = requests.post(
                    wp_media_url,
                    data=f,
                    params={
                        'slug': slug,
                        'title': title,
                        'description': description
                    },
                    headers=headers,
                    auth=req_auth
                )

                if not (200 <= r.status_code <= 299):
                    return raise_error(r.content)

                log_response = r.content.decode('utf-8', 'replace')
                response = json.loads(r.content)

                dbg = '{0} pushed to Wordpress.'.format(filename)
                protocol.append(dbg)
                __tmp(dbg)

                events.append({
                    'pollable': False,
                    'base_type': 'asset',
                    'object_id': fo.get('eas_id'),
                    'event_info': {
                        'wordpress': r.content,  # response,
                        'version': fo.get('eas_version'),
                        'class': fo.get('eas_fileclass'),
                        'system_object_id': fo.get('system_object_id'),
                    }
                })
        else:
            # update file

            # update the first file only
            update_file_id = files_by_slug[0]['id']

            r = requests.post(
                wp_media_url + '/' + str(update_file_id),
                params={
                    'title': title,
                    'description': description,
                },
                headers=headers,
                auth=req_auth
            )

            log_response = r.content.decode('utf-8', 'replace')
            response = json.loads(r.content)

            if not (200 <= r.status_code <= 299):
                return raise_error(response.get('message').encode('utf-8'))

            dbg = '{0} updated in Wordpress. ID: {1}'.format(filename, update_file_id)
            protocol.append(dbg)
            __tmp(dbg)

        if log_response:
            events.append({
                'name': 'WORDPRESS_SYNC',
                'pollable': False,
                'base_type': 'asset',
                'object_id': fo.get('eas_id'),
                'event_info': {
                    'wordpress': log_response,
                    'version': fo.get('eas_version'),
                    'class': fo.get('eas_fileclass'),
                    'system_object_id': fo.get('system_object_id'),
                }
            })

    return events, protocol
