"""
Handle matching data in a FileNameParts (likely generated by a namer_file_parser.py) to
look up metadata (actors, studio, creation data, posters, etc) from the porndb.
"""

import argparse
import itertools
import os
from pathlib import Path
import json
from random import choices
import string
from datetime import timedelta, date
import pathlib
import re
import sys
from typing import List
import logging
from types import SimpleNamespace
import urllib
import urllib.request
import rapidfuzz
import requests
from namer.types import LookedUpFileInfo, Performer, FileNameParts, ComparisonResult, default_config
from namer.filenameparser import parse_file_name

logger = logging.getLogger('metadata')

def __evaluate_match(name_parts: FileNameParts, looked_up: LookedUpFileInfo) -> ComparisonResult:
    release_date = name_parts.date is None or name_parts.date == looked_up.date
    site = (name_parts.site is None or
        re.sub(r' ', '', name_parts.site.capitalize()) in re.sub( r' ', '', looked_up.site.capitalize()))
    all_performers = list(map(lambda p: p.name, looked_up.performers))
    all_performers.insert(0, looked_up.name)
    powerset = (combo for r in range(1,len(all_performers) + 1) for combo in itertools.combinations(all_performers, r))
    ratios = rapidfuzz.process.extractOne(name_parts.name, map(" ".join, powerset))
    return ComparisonResult(
        name=ratios[0],
        name_match=ratios[1],
        datematch=release_date,
        sitematch=site,
        name_parts=name_parts,
        looked_up=looked_up)

def __update_results(results: List[ComparisonResult], name_parts: FileNameParts, authtoken: str,
        skipdate: bool=False, skipname: bool=False):
    if len(results) == 0 or not results[0].is_match():
        for match_attempt in __get_metadataapi_net_fileinfo(name_parts, authtoken, skipdate, skipname):
            result = __evaluate_match(name_parts, match_attempt)
            results.append(result)
        results = sorted(results, key=__match_percent, reverse=True)

def __metadata_api_lookup(name_parts: FileNameParts, authtoken: str) -> List[ComparisonResult]:
    results = []
    __update_results(results, name_parts, authtoken)
    __update_results(results, name_parts, authtoken, skipdate=True)
    __update_results(results, name_parts, authtoken, skipdate=True, skipname=True)
    __update_results(results, name_parts, authtoken, skipname=True)

    if len(results) == 0 or not results[-1].is_match():
        name_parts.date =  (date.fromisoformat(name_parts.date)+timedelta(days=-1)).isoformat()
        logger.info("Not found, trying 1 day before: %s",name_parts)
        __update_results(results, name_parts, authtoken)
        __update_results(results, name_parts, authtoken, skipdate=False, skipname=True)

    if len(results) == 0 or not results[-1].is_match():
        name_parts.date = (date.fromisoformat(name_parts.date)+timedelta(days=2)).isoformat()
        logger.info("Not found, trying 1 day after: %s",name_parts)
        __update_results(results, name_parts, authtoken)
        __update_results(results, name_parts, authtoken, skipdate=False, skipname=True)
    return results


def __match_percent(result: ComparisonResult) -> float:
    addvalue=0.00
    if result.is_match() is True:
        addvalue=1000.00
    value = result.name_match + addvalue
    logger.info("Name match was %.2f for %s", value, result.name)
    return value


def __get_response_json_object(url: str, authtoken: str) -> str:
    """
    returns json object with info
    """
    headers = {"Authorization": f"Bearer {authtoken}",
               "Content-Type": "application/json",
               "Accept": "application/json",
               'User-Agent': 'namer-1'}
    try:
        with requests.get(url, headers=headers) as response:
            response.raise_for_status()
            return response.text
    except requests.exceptions.RequestException as ex:
        logger.warning(ex)
        return None

def get_poster(url: str, authtoken: str, video_file: Path) -> Path:
    """
    returns json object with info
    """
    if url.startswith("http"):
        headers = {"Authorization": f"Bearer {authtoken}",
                'User-Agent': 'namer-1'}
        random = ''.join(choices(population=string.ascii_uppercase + string.digits, k=10))
        file =  video_file.parent / ( video_file.stem + "_" + random+"_poster" + pathlib.Path(url).suffix )
        try:
            with requests.get(url, headers=headers) as response:
                #Not sure how to avoid this 406, tried all kinds of Accept/User-Agent...
                #response.raise_for_status()
                with open(file, "wb") as binary_file:
                    # Write bytes to file
                    binary_file.write(response.content)
                    return file
        except requests.exceptions.RequestException as ex:
            logger.warning(ex)
            return None
    else:
        poster = (video_file.parent / url).resolve()
        return poster if poster.exists() and poster.is_file() else None


def __jsondata_to_fileinfo(data, url, json_response, name_parts) -> LookedUpFileInfo:
    file_info = LookedUpFileInfo()
    file_info.uuid = data._id # pylint: disable=protected-access
    file_info.name = data.title
    file_info.description = data.description
    file_info.date = data.date
    file_info.source_url = data.url
    file_info.poster_url = data.poster
    file_info.site = data.site.name
    file_info.look_up_site_id = data._id # pylint: disable=protected-access
    for json_performer in data.performers:
        performer = Performer()
        if hasattr(json_performer, "parent") and hasattr(json_performer.parent, "extras"):
            performer.role = json_performer.parent.extras.gender
        performer.name = json_performer.name
        file_info.performers.append(performer)
    file_info.original_query=url
    file_info.origninal_response=json_response
    file_info.original_parsed_filename=name_parts
    tags = []
    if hasattr(data, "tags"):
        for tag in data.tags:
            tags.append(tag.name)
        file_info.tags = tags
    return file_info


def __metadataapi_response_to_data(json_object, url, json_response, name_parts) -> List[LookedUpFileInfo]:
    file_infos = []
    if hasattr(json_object, "data"):
        if isinstance(json_object.data, list):
            for data in json_object.data:
                found_file_info = __jsondata_to_fileinfo(data, url, json_response, name_parts)
                file_infos.append(found_file_info)
        else:
            file_infos.append(__jsondata_to_fileinfo(json_object.data, url, json_response, name_parts))
    return file_infos


def __build_url(site:str=None, release_date:str=None, name:str=None, uuid:str=None) -> str:
    query = ""
    if uuid is not None:
        query = "/"+str(uuid)
    else:
        query="?parse="
        if site is not None:
            query += urllib.parse.quote(re.sub(r' ', '.', site))+"."
        if release_date is not None:
            query += release_date+"."
        if name is not None:
            query += urllib.parse.quote(re.sub(r' ', '.', name))
        query+="&limit=25"
    return f"https://api.metadataapi.net/scenes{query}"


def __get_metadataapi_net_fileinfo(name_parts: FileNameParts, authtoken: str, skipdate: bool,
        skipname: bool) -> List[LookedUpFileInfo]:
    release_date = name_parts.date if not skipdate else None
    name = name_parts.name if not skipname else None
    url = __build_url(name_parts.site, release_date, name)
    logger.info("Querying: %s",url)
    json_response = __get_response_json_object(url, authtoken)
    file_infos = []
    if json_response is not None and json_response.strip() != '':
        logger.debug("json_repsonse: \n%s", json_response)
        json_obj = json.loads(json_response, object_hook=lambda d: SimpleNamespace(**d))
        formatted = json.dumps(json.loads(json_response), indent=4, sort_keys=True)
        file_infos = __metadataapi_response_to_data(json_obj, url, formatted, name_parts)
    return file_infos


def __get_complete_metadatapi_net_fileinfo(name_parts: FileNameParts, uuid: str, authtoken: str) -> LookedUpFileInfo:
    url = __build_url(uuid=uuid)
    logger.info("Querying: %s",url)
    json_response = __get_response_json_object(url, authtoken)
    file_infos = []
    if json_response is not None and json_response.strip() != '':
        logger.debug("json_repsonse: \n%s",json_response)
        json_obj = json.loads(json_response, object_hook=lambda d: SimpleNamespace(**d))
        formatted = json.dumps(json.loads(json_response), indent=4, sort_keys=True)
        file_infos = __metadataapi_response_to_data(json_obj, url, formatted, name_parts)
    if len(file_infos) > 0:
        return file_infos[0]
    return None

def match(file_name_parts: FileNameParts, porndb_token: str) -> List[ComparisonResult]:
    """
    Give parsed file name parts, and a porndb token, returns a sorted list of possible matches.
    Matches will appear first.
    """
    comparison_results = __metadata_api_lookup(file_name_parts, porndb_token)
    comparison_results = sorted(comparison_results, key=__match_percent, reverse=True)
    # Works around the porndb not returning all info on search queries by looking up the full data
    # with the uuid of the best match.
    if len(comparison_results) > 0 and comparison_results[0].is_match() is True:
        file_infos =  __get_complete_metadatapi_net_fileinfo(file_name_parts, comparison_results[0].looked_up.uuid ,porndb_token)
        if file_infos is not None:
            comparison_results[0].looked_up = file_infos
    return comparison_results


def main(argslist: List[str]):
    """
    Looks up metadata from metadataapi.net base on file name.
    """
    description = """
    Command line interface to look up a suggested name for an adult movie file based on an input string
    that is parsable by namer_file_parser.py
    """
    parser = argparse.ArgumentParser(description=description)
    parser.add_argument("-c", "--configfile", help="override location for a configuraion file.", type=pathlib.Path)
    parser.add_argument("-f", "--file", help="File we want to provide a match name for.", required=True, type=pathlib.Path)
    parser.add_argument("-t", "--token", help="Access token.", required=True)
    parser.add_argument("-j", "--jsonfile", help="write returned json to this file.", type=pathlib.Path)
    parser.add_argument("-v", "--verbose", help="verbose, print logs", action="store_true")
    args = parser.parse_args(args=argslist)
    logger_level = logging.ERROR
    if args.verbose:
        logger_level=logging.DEBUG
    logging.basicConfig(level=logger_level)
    file_name = parse_file_name(os.path.basename(args.file), default_config().name_parser)
    match_results = match(file_name, args.token)
    if len(match_results) > 0 and match_results[0].is_match() is True:
        print(match_results[0].looked_up.new_file_name(default_config().inplace_name))
        if args.jsonfile is not None:
            Path(args.jsonfile).write_text(match_results[0].looked_up.origninal_response, encoding="UTF-8")

if __name__ == "__main__":
    main(sys.argv[1:])
