from datetime import datetime
import hashlib
import json
import os
import requests
from requests.auth import HTTPBasicAuth
from requests_oauthlib import OAuth2

import fylr_lib_plugin_python3.util as fylr_util


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


def check_response(r: requests.Response, print_body: bool = True):
    try:
        r.raise_for_status()
    except Exception as e:
        if print_body:
            raise Exception(f'status: {r.status_code}; body: {r.content.decode()}')
        raise Exception(f'status: {r.status_code}')


def discover_wp_api_url(url: str) -> str:
    # need to do a api discovery to find out at which endpoint the api is registered
    # send a HEAD request to the server and parse the Link headers
    try:
        resp = requests.head(url)
        check_response(resp, print_body=False)

        api_url = ''
        for link in resp.links.values():
            if link.get('rel', '').startswith('https://api.w.org'):
                api_url = link.get('url', '')
                if api_url:
                    break

        if not api_url:
            raise Exception('no REST api found')

        while api_url.endswith('/'):
            api_url = api_url[:-1]
        return api_url + '/wp/v2'

    except Exception as e:
        raise Exception(f'api discovery failed: {e}')


def build_req_auth(auth: dict):
    auth_type = auth.get('auth_type')
    if auth_type == 'http-auth':
        return HTTPBasicAuth(
            auth.get('http_auth_login', ''),
            auth.get('http_auth_password', ''),
        )
    if auth_type == 'oauth2':
        return OAuth2(
            client_id=auth.get('oauth2_client_id', ''),
            token={
                'access_token': auth.get('oauth2_access_token', ''),
                'token_type': auth.get('oauth2_token_type', ''),
            },
        )

    raise Exception(f'invalid auth_type: {auth_type}. expect [http-auth, oauth2]')


def __download_export_file(url, f_path):
    try:
        resp = requests.get(url)
        check_response(resp, print_body=False)

        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)

    except Exception as e:
        raise Exception(f'could not get file from fylr: {e}')


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 = 0,  # only needed for fylr
    transport_uuid: int = 0,  # only needed for fylr
    api_callback_url: str = '',  # only needed for fylr
) -> tuple[list, list, str]:

    events = []
    protocol = []

    if not files:
        protocol.append('No files to upload to wordpress')
        return [], protocol, 'failed'

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

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

            # from easydb5
            fileclass = fo.get('eas_fileclass', '')
            if not fileclass:
                # from fylr
                fileclass = fo.get('export_file_internal', {}).get('content_type', '')
                fileclass = fileclass.split('/')[0]
                needs_file_download = True

            if fileclass != 'image':
                protocol.append(f'{filename} skipped: wrong fileclass {fileclass}')
                count_skipped += 1
                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
                file_url = f'{api_callback_url}/api/v1/export/{export_id}/uuid/{transport_uuid}/file/{path}'
                # protocol.append(f'load file from export: {file_url}')
                __download_export_file(file_url, path)

            headers = {
                'Content-Type': 'application/octet-stream',
                'Content-Disposition': f'attachment; filename="{filename}"',
            }

            title = filename
            description = f'Imported from {source_name}: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}. User: {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()

            slug_params = {
                'slug': slug,
                'title': title,
                'description': description,
            }

            # let's see if the file exists in WP already
            protocol.append(
                f'{filename} check if this file already exists in Wordpress'
            )
            resp = requests.get(
                wp_media_url,
                params=slug_params,
                auth=req_auth,
            )
            check_response(resp)
            files_by_slug = resp.json()

            if len(files_by_slug) == 0:
                # file is new, push it
                with open(path, 'rb') as f:
                    resp = requests.post(
                        wp_media_url,
                        data=f,
                        params=slug_params,
                        headers=headers,
                        auth=req_auth,
                    )
                    check_response(resp)
                    body_js = resp.json()

                    log_response = body_js

                    protocol.append(f'{filename} uploaded to Wordpress')

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

                    count_success += 1
            else:
                # update file

                # update the first file only
                update_file_id = files_by_slug[0]['id']
                resp = requests.post(
                    # NOTICE: do not use urljoin() here!
                    wp_media_url + '/' + str(update_file_id),
                    data=None,
                    params={
                        'title': title,
                        'description': description,
                    },
                    headers=headers,
                    auth=req_auth,
                )
                check_response(resp)
                body_js = resp.json()

                log_response = body_js

                protocol.append(
                    f'{filename} updated in Wordpress. ID: {update_file_id}'
                )

                count_success += 1

            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'),
                        },
                    }
                )

        except Exception as e:
            protocol.append(f'{path} failed to upload to Wordpress: {e}')

    count_failed = len(files) - count_success - count_skipped
    if count_failed:
        protocol.append(
            f'{count_failed}/{len(files)} files failed to upload to Wordpress'
        )

    protocol.append(f'{count_skipped}/{len(files)} files skipped')
    protocol.append(f'{count_success}/{len(files)} files uploaded to Wordpress')

    return events, protocol, 'failed' if count_failed else 'done'


# -------------------------
# fylr only


def get_wp_instance_and_auth_by_uuid(
    config_json: dict,
    wp_inst_uuid: str,
) -> tuple[dict, dict]:
    try:
        wp_conf = config_json.get('wordpress', {})
        if not wp_conf:
            raise Exception('wordpress config not found')

        for wp_inst in wp_conf.get('wp_instances', []):
            wp_auth = json.loads(wp_inst.get('auth', '{}'))
            if wp_auth.get('uuid', '') == wp_inst_uuid:
                return wp_inst, wp_auth

        raise Exception('wordpress instance not found')

    except Exception as e:
        raise Exception(f'no config for wordpress instance by uuid {wp_inst_uuid}: {e}')
