# coding=utf-8
# fmt: off

import logging
import operator
import ast

from datetime import datetime, timedelta
from functools import reduce
from sqlalchemy import and_, or_

from app.config import settings
from app.database import (get_exclusion_clause, get_audio_profile_languages, TableShows, TableEpisodes, TableMovies,
     TableHistory, TableHistoryMovie, database, select, func, get_profiles_list, TableEpisodesSubtitles,
     TableMoviesSubtitles)
from app.jobs_queue import jobs_queue
from app.get_providers import get_providers
from app.notifier import send_notifications, send_notifications_movie
from radarr.history import history_log_movie
from sonarr.history import history_log
from subtitles.indexer.movies import store_subtitles_movie
from subtitles.indexer.series import store_subtitles
from utilities.path_mappings import path_mappings
from .download import generate_subtitles
from app.event_handler import event_stream


def upgrade_subtitles(wait_for_completion=False):
    use_sonarr = settings.general.use_sonarr
    use_radarr = settings.general.use_radarr

    if use_sonarr:
        upgrade_episodes_subtitles(wait_for_completion=wait_for_completion)

    if use_radarr:
        upgrade_movies_subtitles(wait_for_completion=wait_for_completion)

    logging.info('BAZARR Finished searching for Subtitles to upgrade. Check History for more information.')


def upgrade_episodes_subtitles(job_id=None, wait_for_completion=False):
    if not job_id:
        jobs_queue.add_job_from_function("Trying to upgrade episodes subtitles", is_progress=True,
                                         wait_for_completion=wait_for_completion)
        return

    episodes_to_upgrade = get_upgradable_episode_subtitles()
    episodes_data = [{
        'id': x.id,
        'seriesTitle': x.seriesTitle,
        'season': x.season,
        'episode': x.episode,
        'title': x.title,
        'language': x.language,
        'audio_language': x.audio_language,
        'video_path': x.video_path,
        'sceneName': x.sceneName,
        'score': x.score,
        'sonarrEpisodeId': x.sonarrEpisodeId,
        'sonarrSeriesId': x.sonarrSeriesId,
        'subtitles_path': x.subtitles_path,
        'path': x.path,
        'profileId': x.profileId,
        'external_subtitles': x.external_subtitles,
    } for x in database.execute(
        select(TableHistory.id,
               TableShows.title.label('seriesTitle'),
               TableEpisodes.season,
               TableEpisodes.episode,
               TableEpisodes.title,
               TableHistory.language,
               TableEpisodes.audio_language,
               TableHistory.video_path,
               TableEpisodes.sceneName,
               TableHistory.score,
               TableHistory.sonarrEpisodeId,
               TableHistory.sonarrSeriesId,
               TableHistory.subtitles_path,
               TableEpisodes.path,
               TableShows.profileId,
               TableEpisodesSubtitles.path.label('external_subtitles'))
        .select_from(TableHistory)
        .join(TableShows, onclause=TableHistory.sonarrSeriesId == TableShows.sonarrSeriesId)
        .join(TableEpisodes, onclause=TableHistory.sonarrEpisodeId == TableEpisodes.sonarrEpisodeId)
        .join(TableEpisodesSubtitles, onclause=TableHistory.sonarrEpisodeId == TableEpisodesSubtitles.sonarrEpisodeId)
        .where(TableEpisodesSubtitles.path.is_not(None)))
    .all() if _language_still_desired(x.language, x.profileId) and x.video_path == x.path]

    for item in episodes_data:
        # do not consider subtitles that do not exist on disk anymore
        if item['subtitles_path'] != item['external_subtitles']:
            continue

        # Mark upgradable and get original_id
        item.update({'original_id': episodes_to_upgrade.get(item['id'])})
        item.update({'upgradable': item['id'] in episodes_to_upgrade.keys()})

        # cleanup the unused attributes
        del item['path']
        del item['external_subtitles']

    # Make sure to keep only upgradable episode subtitles
    episodes_data = [x for x in episodes_data if 'upgradable' in x and x['upgradable']]

    count_episode_to_upgrade = len(episodes_data)
    jobs_queue.update_job_progress(job_id=job_id, progress_max=count_episode_to_upgrade)

    if count_episode_to_upgrade == 0:
        jobs_queue.update_job_progress(job_id=job_id, progress_value='max')

    for i, episode in enumerate(episodes_data, start=1):
        providers_list = get_providers()

        jobs_queue.update_job_progress(job_id=job_id, progress_value=i,
                                       progress_message=f'{episode["seriesTitle"]} - S{episode["season"]:02d}E'
                                                        f'{episode["episode"]:02d} - {episode["title"]}')

        if not providers_list:
            logging.info("BAZARR All providers are throttled")
            return

        language, is_forced, is_hi = parse_language_string(episode['language'])
        if is_hi and not _is_hi_required(language, episode['profileId']):
            is_hi = 'False'

        audio_language_list = get_audio_profile_languages(episode['audio_language'])
        if len(audio_language_list) > 0:
            audio_language = audio_language_list[0]['name']
        else:
            audio_language = 'None'

        result = list(generate_subtitles(path_mappings.path_replace(episode['video_path']),
                                         [(language, is_hi, is_forced)],
                                         audio_language,
                                         str(episode['sceneName']),
                                         episode['seriesTitle'],
                                         'series',
                                         episode['profileId'],
                                         forced_minimum_score=int(episode['score']) + 1,
                                         is_upgrade=True,
                                         previous_subtitles_to_delete=path_mappings.path_replace(
                                             episode['subtitles_path']),
                                         job_id=job_id))

        if result:
            if isinstance(result, list) and len(result):
                result = result[0]
            if isinstance(result, tuple) and len(result):
                result = result[0]
            store_subtitles(episode['sonarrEpisodeId'])
            history_log(3, episode['sonarrSeriesId'], episode['sonarrEpisodeId'], result,
                        upgraded_from_id=episode['original_id'] or episode['id'])  # we use or to handle None values on initial upgrade
            send_notifications(episode['sonarrSeriesId'], episode['sonarrEpisodeId'], result.message)
            event_stream(type="episode-history")
    jobs_queue.update_job_name(job_id=job_id, new_job_name='Tried to upgrade episodes subtitles')


def upgrade_movies_subtitles(job_id=None, wait_for_completion=False):
    if not job_id:
        jobs_queue.add_job_from_function("Trying to upgrade movies subtitles", is_progress=True,
                                         wait_for_completion=wait_for_completion)
        return

    movies_to_upgrade = get_upgradable_movies_subtitles()
    movies_data = [{
        'id': x.id,
        'title': x.title,
        'language': x.language,
        'audio_language': x.audio_language,
        'video_path': x.video_path,
        'sceneName': x.sceneName,
        'score': x.score,
        'radarrId': x.radarrId,
        'path': x.path,
        'profileId': x.profileId,
        'subtitles_path': x.subtitles_path,
        'external_subtitles': x.external_subtitles,
    } for x in database.execute(
        select(TableHistoryMovie.id,
               TableMovies.title,
               TableHistoryMovie.language,
               TableMovies.audio_language,
               TableHistoryMovie.video_path,
               TableMovies.sceneName,
               TableHistoryMovie.score,
               TableHistoryMovie.radarrId,
               TableHistoryMovie.subtitles_path,
               TableMovies.path,
               TableMovies.profileId,
               TableMoviesSubtitles.path.label('external_subtitles'))
        .select_from(TableHistoryMovie)
        .join(TableMovies, onclause=TableHistoryMovie.radarrId == TableMovies.radarrId)
        .join(TableMoviesSubtitles, onclause=TableHistoryMovie.radarrId == TableMoviesSubtitles.radarrId)
        .where(TableMoviesSubtitles.path.is_not(None)))
    .all() if _language_still_desired(x.language, x.profileId) and x.video_path == x.path]

    for item in movies_data:
        # do not consider subtitles that do not exist on disk anymore
        if item['subtitles_path'] != item['external_subtitles']:
            continue

        # Mark upgradable and get original_id
        item.update({'original_id': movies_to_upgrade.get(item['id'])})
        item.update({'upgradable': item['id'] in movies_to_upgrade.keys()})

        # cleanup the unused attributes
        del item['path']
        del item['external_subtitles']

    # Make sure to keep only upgradable movie subtitles
    movies_data = [x for x in movies_data if 'upgradable' in x and x['upgradable']]

    count_movie_to_upgrade = len(movies_data)
    jobs_queue.update_job_progress(job_id=job_id, progress_max=count_movie_to_upgrade)

    if count_movie_to_upgrade == 0:
        jobs_queue.update_job_progress(job_id=job_id, progress_value='max')

    for i, movie in enumerate(movies_data, start=1):
        providers_list = get_providers()

        jobs_queue.update_job_progress(job_id=job_id, progress_value=i, progress_message=movie['title'])

        if not providers_list:
            logging.info("BAZARR All providers are throttled")
            return

        language, is_forced, is_hi = parse_language_string(movie['language'])
        if is_hi and not _is_hi_required(language, movie['profileId']):
            is_hi = 'False'

        audio_language_list = get_audio_profile_languages(movie['audio_language'])
        if len(audio_language_list) > 0:
            audio_language = audio_language_list[0]['name']
        else:
            audio_language = 'None'

        result = list(generate_subtitles(path_mappings.path_replace_movie(movie['video_path']),
                                         [(language, is_hi, is_forced)],
                                         audio_language,
                                         str(movie['sceneName']),
                                         movie['title'],
                                         'movie',
                                         movie['profileId'],
                                         forced_minimum_score=int(movie['score']) + 1,
                                         is_upgrade=True,
                                         previous_subtitles_to_delete=path_mappings.path_replace_movie(
                                             movie['subtitles_path']),
                                         job_id=job_id))
        if result:
            if isinstance(result, list) and len(result):
                result = result[0]
            if isinstance(result, tuple) and len(result):
                result = result[0]
            store_subtitles_movie(movie['radarrId'])
            history_log_movie(3, movie['radarrId'], result, upgraded_from_id=movie['original_id'] or movie['id'])  # we use or to handle None values on initial upgrade
            send_notifications_movie(movie['radarrId'], result.message)
            event_stream(type="movie-history")
    jobs_queue.update_job_name(job_id=job_id, new_job_name='Tried to upgrade movies subtitles')


def get_queries_condition_parameters():
    days_to_upgrade_subs = settings.general.days_to_upgrade_subs
    minimum_timestamp = (datetime.now() - timedelta(days=int(days_to_upgrade_subs)))

    if settings.general.upgrade_manual:
        query_actions = [1, 2, 3, 4, 6]
    else:
        query_actions = [1, 3]

    return [minimum_timestamp, query_actions]


def parse_language_string(language_string):
    if language_string.endswith('forced'):
        language = language_string.split(':')[0]
        is_forced = "True"
        is_hi = "False"
    elif language_string.endswith('hi'):
        language = language_string.split(':')[0]
        is_forced = "False"
        is_hi = "True"
    else:
        language = language_string.split(':')[0]
        is_forced = "False"
        is_hi = "False"

    return [language, is_forced, is_hi]


def get_upgradable_episode_subtitles(history_id_list=None):
    if not settings.general.upgrade_subs:
        # return an empty set of rows
        logging.debug("Subtitles upgrade is disabled so we wont go further.")
        return {}

    minimum_timestamp, query_actions = get_queries_condition_parameters()
    logging.debug(f"Minimum timestamp used for subtitles upgrade: {minimum_timestamp}")
    logging.debug(f"These actions are considered for subtitles upgrade: {query_actions}")

    logging.debug("Determining upgradable episode subtitles")
    last_since_delete_subquery = (
        select(TableHistory.video_path,
               TableHistory.language,
               func.max(TableHistory.id).label('last_delete_id'))
        .group_by(TableHistory.video_path, TableHistory.language)
        .where(TableHistory.action == 0)
        .subquery()
    )

    max_id_timestamp = select(TableHistory.video_path,
                              TableHistory.language,
                              func.max(TableHistory.timestamp).label('timestamp')) \
        .outerjoin(last_since_delete_subquery, onclause=and_(
            TableHistory.video_path == last_since_delete_subquery.c.video_path,
            TableHistory.language == last_since_delete_subquery.c.language)) \
        .group_by(TableHistory.video_path, TableHistory.language) \
        .where(and_(TableHistory.action.in_(query_actions),
                    or_(last_since_delete_subquery.c.last_delete_id.is_(None),
                        TableHistory.id > last_since_delete_subquery.c.last_delete_id))) \
        .distinct() \
        .subquery()

    upgradable_episodes_conditions = [(TableHistory.action.in_(query_actions)),
                                      (TableHistory.timestamp > minimum_timestamp),
                                      or_(and_(TableHistory.score.is_(None), TableHistory.action == 6),
                                      (TableHistory.score < TableHistory.score_out_of - 3))]
    upgradable_episodes_conditions += get_exclusion_clause('series')
    subtitles_to_upgrade = database.execute(
        select(TableHistory.id,
               TableHistory.video_path,
               TableHistory.language,
               TableHistory.upgradedFromId)
        .select_from(TableHistory)
        .join(TableShows, onclause=TableHistory.sonarrSeriesId == TableShows.sonarrSeriesId)
        .join(TableEpisodes, onclause=TableHistory.sonarrEpisodeId == TableEpisodes.sonarrEpisodeId)
        .join(max_id_timestamp, onclause=and_(TableHistory.video_path == max_id_timestamp.c.video_path,
                                              TableHistory.language == max_id_timestamp.c.language,
                                              max_id_timestamp.c.timestamp == TableHistory.timestamp))
        .where(reduce(operator.and_, upgradable_episodes_conditions))
        .order_by(TableHistory.timestamp.desc())) \
        .all()
    logging.debug(f"{len(subtitles_to_upgrade)} subtitles are candidates and we've selected the latest timestamp for "
                  f"each of them.")

    upgradable_episode_subtitles = {}
    for subtitle_to_upgrade in subtitles_to_upgrade:
        # exclude subtitles that are not in history_id_list if provided
        if history_id_list and subtitle_to_upgrade.id not in history_id_list:
            continue

        # exclude subtitles with ID that as been "upgraded from" and shouldn't be considered
        if any(x.upgradedFromId == subtitle_to_upgrade.id for x in subtitles_to_upgrade):
            logging.debug(f"TableHistory ID {subtitle_to_upgrade.id} is the original subtitles event and has already "
                          f"been upgraded so we'll skip it.")
            continue

        # check if we have the original subtitles ID in database
        if subtitle_to_upgrade.upgradedFromId:
            upgradable_episode_subtitles.update({subtitle_to_upgrade.id: subtitle_to_upgrade.upgradedFromId})
            logging.debug(f"The original subtitles ID for TableHistory ID {subtitle_to_upgrade.id} stored in DB is: "
                          f"{subtitle_to_upgrade.upgradedFromId}")
            continue
        else:
            upgradable_episode_subtitles.update({subtitle_to_upgrade.id: None})
            logging.debug(f"It seems to be the first time we've seen this TableHistory ID {subtitle_to_upgrade.id}.")
            continue

    logging.debug(f"We've found {len(upgradable_episode_subtitles)} episode subtitles IDs to be upgradable")
    return upgradable_episode_subtitles


def get_upgradable_movies_subtitles(history_id_list=None):
    if not settings.general.upgrade_subs:
        # return an empty set of rows
        logging.debug("Subtitles upgrade is disabled so we won't go further.")
        return {}

    minimum_timestamp, query_actions = get_queries_condition_parameters()
    logging.debug(f"Minimum timestamp used for subtitles upgrade: {minimum_timestamp}")
    logging.debug(f"These actions are considered for subtitles upgrade: {query_actions}")

    logging.debug("Determining upgradable movie subtitles")
    last_since_delete_subquery = (
        select(TableHistoryMovie.video_path,
               TableHistoryMovie.language,
               func.max(TableHistoryMovie.id).label('last_delete_id'))
        .group_by(TableHistoryMovie.video_path, TableHistoryMovie.language)
        .where(TableHistoryMovie.action == 0)
        .subquery()
    )

    max_id_timestamp = select(TableHistoryMovie.video_path,
                              TableHistoryMovie.language,
                              func.max(TableHistoryMovie.timestamp).label('timestamp')) \
        .outerjoin(last_since_delete_subquery, onclause=and_(
            TableHistoryMovie.video_path == last_since_delete_subquery.c.video_path,
            TableHistoryMovie.language == last_since_delete_subquery.c.language)) \
        .group_by(TableHistoryMovie.video_path, TableHistoryMovie.language) \
        .where(and_(TableHistoryMovie.action.in_(query_actions),
                    or_(last_since_delete_subquery.c.last_delete_id.is_(None),
                        TableHistoryMovie.id > last_since_delete_subquery.c.last_delete_id))) \
        .distinct() \
        .subquery()

    upgradable_movies_conditions = [(TableHistoryMovie.action.in_(query_actions)),
                                    (TableHistoryMovie.timestamp > minimum_timestamp),
                                    or_(and_(TableHistoryMovie.score.is_(None), TableHistoryMovie.action == 6),
                                    (TableHistoryMovie.score < TableHistoryMovie.score_out_of - 3))]
    upgradable_movies_conditions += get_exclusion_clause('movie')
    subtitles_to_upgrade = database.execute(
        select(TableHistoryMovie.id,
               TableHistoryMovie.video_path,
               TableHistoryMovie.language,
               TableHistoryMovie.upgradedFromId)
        .select_from(TableHistoryMovie)
        .join(TableMovies, onclause=TableHistoryMovie.radarrId == TableMovies.radarrId)
        .join(max_id_timestamp, onclause=and_(TableHistoryMovie.video_path == max_id_timestamp.c.video_path,
                                              TableHistoryMovie.language == max_id_timestamp.c.language,
                                              max_id_timestamp.c.timestamp == TableHistoryMovie.timestamp))
        .where(reduce(operator.and_, upgradable_movies_conditions))
        .order_by(TableHistoryMovie.timestamp.desc())) \
        .all()
    logging.debug(f"{len(subtitles_to_upgrade)} subtitles are candidates and we've selected the latest timestamp for "
                  f"each of them.")

    upgradable_movie_subtitles = {}
    for subtitle_to_upgrade in subtitles_to_upgrade:
        # exclude subtitles that are not in history_id_list if provided
        if history_id_list and subtitle_to_upgrade.id not in history_id_list:
            continue

        # exclude subtitles with ID that as been "upgraded from" and shouldn't be considered
        if any(x.upgradedFromId == subtitle_to_upgrade.id for x in subtitles_to_upgrade):
            logging.debug(f"TableHistoryMovie ID {subtitle_to_upgrade.id} is the original subtitles event and has "
                          f"already been upgraded so we'll skip it.")
            continue

        # check if we have the original subtitles ID in database
        if subtitle_to_upgrade.upgradedFromId:
            upgradable_movie_subtitles.update({subtitle_to_upgrade.id: subtitle_to_upgrade.upgradedFromId})
            logging.debug(f"The original subtitles ID for TableHistoryMovie ID {subtitle_to_upgrade.id} stored in DB "
                          f"is: {subtitle_to_upgrade.upgradedFromId}")
            continue
        else:
            upgradable_movie_subtitles.update({subtitle_to_upgrade.id: None})
            logging.debug(f"It seems to be the first time we've seen this TableHistoryMovie ID "
                          f"{subtitle_to_upgrade.id}.")
            continue

    logging.debug(f"We've found {len(upgradable_movie_subtitles)} movie subtitles IDs to be upgradable")
    return upgradable_movie_subtitles


def _language_still_desired(language, profile_id):
    if not profile_id:
        return False

    profile = get_profiles_list(profile_id)
    if profile and language in _language_from_items(profile['items']):
        return True
    else:
        return False


def _language_from_items(items):
    results = []
    for item in items:
        if item['forced'] == 'True':
            results.append(f'{item["language"]}:forced')
        elif item['hi'] == 'True':
            results.append(f'{item["language"]}:hi')
        else:
            results.append(item['language'])
            results.append(f'{item["language"]}:hi')
    return results


def _is_hi_required(language, profile_id):
    profile = get_profiles_list(profile_id=profile_id)
    for item in profile['items']:
        if language.split(':')[0] == item['language'] and item['hi'] == 'True':
            return True
    return False
