# -*- coding: utf-8 -*-

# This file is part of Tautulli.
#
#  Tautulli is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  Tautulli is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with Tautulli.  If not, see <http://www.gnu.org/licenses/>.

import json
import os

import plexpy
from plexpy import common
from plexpy import database
from plexpy import datatables
from plexpy import helpers
from plexpy import logger
from plexpy import plextv
from plexpy import pmsconnect
from plexpy import session
from plexpy import users
from plexpy.plex import Plex


def refresh_libraries():
    logger.info("Tautulli Libraries :: Requesting libraries list refresh...")

    server_id = plexpy.CONFIG.PMS_IDENTIFIER
    if not server_id:
        logger.error("Tautulli Libraries :: No PMS identifier, cannot refresh libraries. Verify server in settings.")
        return

    library_sections = pmsconnect.PmsConnect().get_library_details()

    if library_sections:
        monitor_db = database.MonitorDatabase()

        library_keys = []
        new_keys = []

        # Keep track of section_id to update is_active status
        section_ids = [common.LIVE_TV_SECTION_ID]  # Live TV library always considered active

        for section in library_sections:
            section_ids.append(helpers.cast_to_int(section['section_id']))

            section_keys = {'server_id': server_id,
                            'section_id': section['section_id']}
            section_values = {'server_id': server_id,
                              'section_id': section['section_id'],
                              'section_name': section['section_name'],
                              'section_type': section['section_type'],
                              'agent': section['agent'],
                              'thumb': section['thumb'],
                              'art': section['art'],
                              'count': section['count'],
                              'parent_count': section.get('parent_count', None),
                              'child_count': section.get('child_count', None),
                              'is_active': section['is_active']
                              }

            result = monitor_db.upsert('library_sections', key_dict=section_keys, value_dict=section_values)

            library_keys.append(section['section_id'])

            if result == 'insert':
                new_keys.append(section['section_id'])

        add_live_tv_library(refresh=True)

        query = "UPDATE library_sections SET is_active = 0 WHERE server_id != ? OR " \
                "section_id NOT IN ({})".format(", ".join(["?"] * len(section_ids)))
        monitor_db.action(query=query, args=[plexpy.CONFIG.PMS_IDENTIFIER] + section_ids)

        new_keys = plexpy.CONFIG.HOME_LIBRARY_CARDS + new_keys
        plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', new_keys)
        plexpy.CONFIG.write()

        logger.info("Tautulli Libraries :: Libraries list refreshed.")
        return True
    else:
        logger.warn("Tautulli Libraries :: Unable to refresh libraries list.")
        return False


def add_live_tv_library(refresh=False):
    monitor_db = database.MonitorDatabase()
    result = monitor_db.select_single("SELECT * FROM library_sections "
                                      "WHERE section_id = ? and server_id = ?",
                                      [common.LIVE_TV_SECTION_ID, plexpy.CONFIG.PMS_IDENTIFIER])

    if result and not refresh or not result and refresh:
        return

    if not refresh:
        logger.info("Tautulli Libraries :: Adding Live TV library to the database.")

    section_keys = {'server_id': plexpy.CONFIG.PMS_IDENTIFIER,
                    'section_id': common.LIVE_TV_SECTION_ID}
    section_values = {'server_id': plexpy.CONFIG.PMS_IDENTIFIER,
                      'section_id': common.LIVE_TV_SECTION_ID,
                      'section_name': common.LIVE_TV_SECTION_NAME,
                      'section_type': 'live',
                      'thumb': common.DEFAULT_LIVE_TV_THUMB,
                      'art': common.DEFAULT_LIVE_TV_ART_FULL,
                      'is_active': 1
                      }

    result = monitor_db.upsert('library_sections', key_dict=section_keys, value_dict=section_values)


def has_library_type(section_type):
    monitor_db = database.MonitorDatabase()
    query = "SELECT * FROM library_sections WHERE section_type = ? AND deleted_section = 0"
    args = [section_type]
    result = monitor_db.select_single(query=query, args=args)
    return bool(result)


def get_collections(section_id=None):
    plex = Plex(token=session.get_session_user_token())
    library = plex.get_library(section_id)

    if library.type not in ('movie', 'show', 'artist'):
        return []

    collections = library.collections()

    collections_list = []
    for collection in collections:
        collection._autoReload = False

        collection_mode = collection.collectionMode
        if collection_mode is None:
            collection_mode = -1

        collection_sort = collection.collectionSort
        if collection_sort is None:
            collection_sort = 0

        collection_dict = {
            'addedAt': helpers.datetime_to_iso(collection.addedAt),
            'art': collection.art,
            'childCount': collection.childCount,
            'collectionMode': collection_mode,
            'collectionPublished': collection.collectionPublished,
            'collectionSort': collection_sort,
            'contentRating': collection.contentRating,
            'guid': collection.guid,
            'librarySectionID': collection.librarySectionID,
            'librarySectionTitle': collection.librarySectionTitle,
            'maxYear': collection.maxYear,
            'minYear': collection.minYear,
            'ratingKey': collection.ratingKey,
            'smart': collection.smart,
            'subtype': collection.subtype,
            'summary': collection.summary,
            'thumb': collection.thumb,
            'title': collection.title,
            'titleSort': collection.titleSort or collection.title,
            'type': collection.type,
            'updatedAt': helpers.datetime_to_iso(collection.updatedAt)
        }
        collections_list.append(collection_dict)

    return collections_list


def get_collections_list(section_id=None, **kwargs):
    if not section_id:
        default_return = {'recordsFiltered': 0,
                          'recordsTotal': 0,
                          'draw': 0,
                          'data': []}
        return default_return

    collections = get_collections(section_id=section_id)

    # Get datatables JSON data
    json_data = helpers.process_json_kwargs(json_kwargs=kwargs['json_data'])

    search_cols = ['title']

    sort_keys = {
        'collectionMode': {
            -1: 'Library Default',
            0: 'Hide collection',
            1: 'Hide items in this collection',
            2: 'Show this collection and its items'
        },
        'collectionSort': {
            0: 'Release date',
            1: 'Alphabetical',
            2: 'Custom'
        }
    }

    results = helpers.process_datatable_rows(
        collections, json_data, default_sort='titleSort',
        search_cols=search_cols, sort_keys=sort_keys)

    data = {
        'recordsFiltered': results['filtered_count'],
        'recordsTotal': results['total_count'],
        'data': results['results'],
        'draw': int(json_data['draw'])
    }

    return data


def get_playlists(section_id=None, user_id=None):
    if user_id and not session.get_session_user_id():
        user_tokens = users.Users().get_tokens(user_id=user_id)
        plex_token = user_tokens['server_token']
    else:
        plex_token = session.get_session_user_token()

    if not plex_token:
        return []

    plex = Plex(token=plex_token)

    if user_id:
        playlists = plex.PlexServer.playlists()
    else:
        library = plex.get_library(section_id)
        playlists = library.playlists()

    playlists_list = []
    for playlist in playlists:
        playlist._autoReload = False

        playlist_dict = {
            'addedAt': helpers.datetime_to_iso(playlist.addedAt),
            'composite': playlist.composite,
            'duration': playlist.duration,
            'guid': playlist.guid,
            'leafCount': playlist.leafCount,
            'librarySectionID': section_id,
            'playlistType': playlist.playlistType,
            'ratingKey': playlist.ratingKey,
            'smart': playlist.smart,
            'summary': playlist.summary,
            'title': playlist.title,
            'type': playlist.type,
            'updatedAt': helpers.datetime_to_iso(playlist.updatedAt),
            'userID': user_id
        }
        playlists_list.append(playlist_dict)

    return playlists_list


def get_playlists_list(section_id=None, user_id=None, **kwargs):
    if not section_id and not user_id:
        default_return = {'recordsFiltered': 0,
                          'recordsTotal': 0,
                          'draw': 0,
                          'data': []}
        return default_return

    playlists = get_playlists(section_id=section_id, user_id=user_id)

    # Get datatables JSON data
    json_data = helpers.process_json_kwargs(json_kwargs=kwargs['json_data'])

    results = helpers.process_datatable_rows(
        playlists, json_data, default_sort='title')

    data = {
        'recordsFiltered': results['filtered_count'],
        'recordsTotal': results['total_count'],
        'data': results['results'],
        'draw': int(json_data['draw'])
    }

    return data


class Libraries(object):

    def __init__(self):
        pass

    def get_datatables_list(self, kwargs=None, grouping=None):
        default_return = {'recordsFiltered': 0,
                          'recordsTotal': 0,
                          'draw': 0,
                          'data': []}

        data_tables = datatables.DataTables()

        custom_where = [['library_sections.deleted_section', 0]]

        if grouping is None:
            grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES

        if session.get_session_shared_libraries():
            custom_where.append(['library_sections.section_id', session.get_session_shared_libraries()])

        group_by = 'session_history.reference_id' if grouping else 'session_history.id'

        columns = ["library_sections.id AS row_id",
                   "library_sections.server_id",
                   "library_sections.section_id",
                   "library_sections.section_name",
                   "library_sections.section_type",
                   "library_sections.count",
                   "library_sections.parent_count",
                   "library_sections.child_count",
                   "library_sections.thumb AS library_thumb",
                   "library_sections.custom_thumb_url AS custom_thumb",
                   "library_sections.art AS library_art",
                   "library_sections.custom_art_url AS custom_art",
                   "COUNT(DISTINCT %s) AS plays" % group_by,
                   "SUM(CASE WHEN session_history.stopped > 0 THEN (session_history.stopped - session_history.started) \
                    ELSE 0 END) - SUM(CASE WHEN session_history.paused_counter IS NULL THEN 0 ELSE \
                    session_history.paused_counter END) AS duration",
                   "MAX(session_history.started) AS last_accessed",
                   "MAX(session_history.id) AS history_row_id",
                   "session_history_metadata.full_title AS last_played",
                   "session_history.rating_key",
                   "session_history_metadata.media_type",
                   "session_history_metadata.thumb",
                   "session_history_metadata.parent_thumb",
                   "session_history_metadata.grandparent_thumb",
                   "session_history_metadata.parent_title",
                   "session_history_metadata.year",
                   "session_history_metadata.media_index",
                   "session_history_metadata.parent_media_index",
                   "session_history_metadata.content_rating",
                   "session_history_metadata.labels",
                   "session_history_metadata.live",
                   "session_history_metadata.added_at",
                   "session_history_metadata.originally_available_at",
                   "session_history_metadata.guid",
                   "library_sections.do_notify",
                   "library_sections.do_notify_created",
                   "library_sections.keep_history",
                   "library_sections.is_active"
                   ]
        try:
            query = data_tables.ssp_query(table_name='library_sections',
                                          columns=columns,
                                          custom_where=custom_where,
                                          group_by=['library_sections.server_id', 'library_sections.section_id'],
                                          join_types=['LEFT OUTER JOIN',
                                                      'LEFT OUTER JOIN',
                                                      'LEFT OUTER JOIN'],
                                          join_tables=['session_history',
                                                       'session_history_metadata',
                                                       'session_history_media_info'],
                                          join_evals=[['session_history.section_id', 'library_sections.section_id'],
                                                      ['session_history.id', 'session_history_metadata.id'],
                                                      ['session_history.id', 'session_history_media_info.id']],
                                          kwargs=kwargs)
        except Exception as e:
            logger.warn("Tautulli Libraries :: Unable to execute database query for get_list: %s." % e)
            return default_return

        result = query['result']

        rows = []
        for item in result:
            if item['media_type'] == 'episode' and item['parent_thumb']:
                thumb = item['parent_thumb']
            elif item['media_type'] == 'episode':
                thumb = item['grandparent_thumb']
            else:
                thumb = item['thumb']

            if item['custom_thumb'] and item['custom_thumb'] != item['library_thumb']:
                library_thumb = item['custom_thumb']
            elif item['library_thumb']:
                library_thumb = item['library_thumb']
            else:
                library_thumb = common.DEFAULT_COVER_THUMB

            if item['custom_art'] and item['custom_art'] != item['library_art']:
                library_art = item['custom_art']
            else:
                library_art = item['library_art']

            row = {'row_id': item['row_id'],
                   'server_id': item['server_id'],
                   'section_id': item['section_id'],
                   'section_name': item['section_name'],
                   'section_type': item['section_type'],
                   'count': item['count'],
                   'parent_count': item['parent_count'],
                   'child_count': item['child_count'],
                   'library_thumb': library_thumb,
                   'library_art': library_art,
                   'plays': item['plays'],
                   'duration': item['duration'],
                   'last_accessed': item['last_accessed'],
                   'history_row_id': item['history_row_id'],
                   'last_played': item['last_played'],
                   'rating_key': item['rating_key'],
                   'media_type': item['media_type'],
                   'thumb': thumb,
                   'parent_title': item['parent_title'],
                   'year': item['year'],
                   'media_index': item['media_index'],
                   'parent_media_index': item['parent_media_index'],
                   'content_rating': item['content_rating'],
                   'labels': item['labels'].split(';') if item['labels'] else (),
                   'live': item['live'],
                   'originally_available_at': item['originally_available_at'],
                   'guid': item['guid'],
                   'do_notify': item['do_notify'],
                   'do_notify_created': item['do_notify_created'],
                   'keep_history': item['keep_history'],
                   'is_active': item['is_active']
                   }

            rows.append(row)

        dict = {'recordsFiltered': query['filteredCount'],
                'recordsTotal': query['totalCount'],
                'data': session.mask_session_info(rows),
                'draw': query['draw']
                }

        return dict

    def get_datatables_media_info(self, section_id=None, section_type=None, rating_key=None, refresh=False, kwargs=None):
        default_return = {'recordsFiltered': 0,
                          'recordsTotal': 0,
                          'draw': 0,
                          'data': [],
                          'filtered_file_size': 0,
                          'total_file_size': 0,
                          'last_refreshed': None}

        if not session.allow_session_library(section_id):
            return default_return

        if section_id and not str(section_id).isdigit():
            logger.warn("Tautulli Libraries :: Datatable media info called but invalid section_id provided.")
            return default_return
        elif rating_key and not str(rating_key).isdigit():
            logger.warn("Tautulli Libraries :: Datatable media info called but invalid rating_key provided.")
            return default_return
        elif not section_id and not rating_key:
            logger.warn("Tautulli Libraries :: Datatable media info called but no input provided.")
            return default_return

        # Get the library details
        library_details = self.get_details(section_id=section_id)
        if library_details['section_id'] == None:
            logger.debug("Tautulli Libraries :: Library section_id %s not found." % section_id)
            return default_return

        if not section_type:
            section_type = library_details['section_type']

        # Get play counts from the database
        monitor_db = database.MonitorDatabase()

        if plexpy.CONFIG.GROUP_HISTORY_TABLES:
            count_by = 'reference_id'
        else:
            count_by = 'id'

        if section_type == 'show' or section_type == 'artist':
            group_by = 'grandparent_rating_key'
        elif section_type == 'season' or section_type == 'album':
            group_by = 'parent_rating_key'
        else:
            group_by = 'rating_key'

        try:
            query = "SELECT MAX(started) AS last_played, COUNT(DISTINCT %s) AS play_count, " \
                    "rating_key, parent_rating_key, grandparent_rating_key " \
                    "FROM session_history " \
                    "WHERE section_id = ? " \
                    "GROUP BY %s " % (count_by, group_by)
            result = monitor_db.select(query, args=[section_id])
        except Exception as e:
            logger.warn("Tautulli Libraries :: Unable to execute database query for get_datatables_media_info2: %s." % e)
            return default_return

        watched_list = {}
        for item in result:
            watched_list[str(item[group_by])] = {'last_played': item['last_played'],
                                                 'play_count': item['play_count']}

        # Import media info cache from json file
        cache_time, rows, library_count = self._load_media_info_cache(section_id=section_id, rating_key=rating_key)

        # If no cache was imported, get all library children items
        cached_items = {d['rating_key']: d['file_size'] for d in rows} if not refresh else {}

        if refresh or not rows:
            pms_connect = pmsconnect.PmsConnect()

            if rating_key:
                library_children = pms_connect.get_library_children_details(rating_key=rating_key,
                                                                            section_id=section_id,
                                                                            section_type=section_type,
                                                                            get_media_info=True)
            elif section_id:
                library_children = pms_connect.get_library_children_details(section_id=section_id,
                                                                            section_type=section_type,
                                                                            get_media_info=True)
            if library_children:
                library_count = library_children['library_count']
                children_list = library_children['children_list']
            else:
                logger.warn("Tautulli Libraries :: Unable to get a list of library items.")
                return default_return

            new_rows = []
            for item in children_list:
                ## TODO: Check list of media info items, currently only grabs first item

                cached_file_size = cached_items.get(item['rating_key'], None)
                file_size = cached_file_size if cached_file_size else item.get('file_size', '')

                row = {'section_id': library_details['section_id'],
                       'section_type': library_details['section_type'],
                       'added_at': item['added_at'],
                       'media_type': item['media_type'],
                       'rating_key': item['rating_key'],
                       'parent_rating_key': item['parent_rating_key'],
                       'grandparent_rating_key': item['grandparent_rating_key'],
                       'title': item['title'],
                       'sort_title': item['sort_title'] or item['title'],
                       'year': item['year'],
                       'media_index': item['media_index'],
                       'parent_media_index': item['parent_media_index'],
                       'thumb': item['thumb'],
                       'container': item.get('container', ''),
                       'bitrate': item.get('bitrate', ''),
                       'video_codec': item.get('video_codec', ''),
                       'video_resolution': item.get('video_resolution', ''),
                       'video_framerate': item.get('video_framerate', ''),
                       'audio_codec': item.get('audio_codec', ''),
                       'audio_channels': item.get('audio_channels', ''),
                       'file_size': file_size
                       }
                new_rows.append(row)

            rows = new_rows
            if not rows:
                return default_return

            # Cache the media info to a json file
            self._save_media_info_cache(section_id=section_id, rating_key=rating_key, rows=rows)

        # Update the last_played and play_count
        for item in rows:
            watched_item = watched_list.get(item['rating_key'], None)
            if watched_item:
                item['last_played'] = watched_item['last_played']
                item['play_count'] = watched_item['play_count']
            else:
                item['last_played'] = None
                item['play_count'] = None

        results = []

        # Get datatables JSON data
        if kwargs.get('json_data'):
            json_data = helpers.process_json_kwargs(json_kwargs=kwargs.get('json_data'))
            #print json_data

        # Search results
        search_value = json_data['search']['value'].lower()
        if search_value:
            searchable_columns = [d['data'] for d in json_data['columns'] if d['searchable']] + ['title']
            for row in rows:
                for k,v in row.items():
                    if k in searchable_columns and search_value in v.lower():
                        results.append(row)
                        break
        else:
            results = rows

        filtered_count = len(results)

        # Sort results
        results = sorted(results, key=lambda k: k['sort_title'].lower())
        sort_order = json_data['order']
        for order in reversed(sort_order):
            sort_key = json_data['columns'][int(order['column'])]['data']
            reverse = True if order['dir'] == 'desc' else False
            if rating_key and sort_key == 'sort_title':
                results = sorted(results, key=lambda k: helpers.cast_to_int(k['media_index']), reverse=reverse)
            elif sort_key in ('file_size', 'bitrate', 'added_at', 'last_played', 'play_count'):
                results = sorted(results, key=lambda k: helpers.cast_to_int(k[sort_key]), reverse=reverse)
            elif sort_key == 'video_resolution':
                results = sorted(results, key=lambda k: helpers.cast_to_int(k[sort_key].replace('4k', '2160p').rstrip('p')), reverse=reverse)
            else:
                results = sorted(results, key=lambda k: k[sort_key].lower(), reverse=reverse)

        total_file_size = sum([helpers.cast_to_int(d['file_size']) for d in results])

        # Paginate results
        results = results[json_data['start']:(json_data['start'] + json_data['length'])]

        filtered_file_size = sum([helpers.cast_to_int(d['file_size']) for d in results])

        output = {
            'recordsFiltered': filtered_count,
            'recordsTotal': int(library_count),
            'data': results,
            'draw': int(json_data['draw']),
            'filtered_file_size': filtered_file_size,
            'total_file_size': total_file_size,
            'last_refreshed': cache_time
        }

        return output

    def get_media_info_file_sizes(self, section_id=None, rating_key=None):
        if not session.allow_session_library(section_id):
            return False

        if section_id and not str(section_id).isdigit():
            logger.warn("Tautulli Libraries :: Datatable media info file size called but invalid section_id provided.")
            return False
        elif rating_key and not str(rating_key).isdigit():
            logger.warn("Tautulli Libraries :: Datatable media info file size called but invalid rating_key provided.")
            return False

        # Get the library details
        library_details = self.get_details(section_id=section_id)
        if library_details['section_id'] == None:
            logger.debug("Tautulli Libraries :: Library section_id %s not found." % section_id)
            return False
        if library_details['section_type'] == 'photo':
            return False

        # Import media info cache from json file
        _, rows, _ = self._load_media_info_cache(section_id=section_id, rating_key=rating_key)

        # Get the total file size for each item
        if rating_key:
            logger.debug("Tautulli Libraries :: Getting file sizes for rating_key %s." % rating_key)
        elif section_id:
            logger.debug("Tautulli Libraries :: Fetting file sizes for section_id %s." % section_id)

        pms_connect = pmsconnect.PmsConnect()

        for item in rows:
            if item['rating_key'] and not item['file_size']:
                file_size = 0

                metadata = pms_connect.get_metadata_children_details(rating_key=item['rating_key'],
                                                                     get_children=True,
                                                                     media_type=item['media_type'],
                                                                     section_id=section_id)

                for child_metadata in metadata:
                    ## TODO: Check list of media info items, currently only grabs first item
                    media_info = media_part_info = {}
                    if 'media_info' in child_metadata and len(child_metadata['media_info']) > 0:
                        media_info = child_metadata['media_info'][0]
                        if 'parts' in media_info and len (media_info['parts']) > 0:
                            media_part_info = next((p for p in media_info['parts'] if p['selected']),
                                                   media_info['parts'][0])

                    file_size += helpers.cast_to_int(media_part_info.get('file_size', 0))

                item['file_size'] = file_size

        # Cache the media info to a json file
        self._save_media_info_cache(section_id=section_id, rating_key=rating_key, rows=rows)

        if rating_key:
            logger.debug("Tautulli Libraries :: File sizes updated for rating_key %s." % rating_key)
        elif section_id:
            logger.debug("Tautulli Libraries :: File sizes updated for section_id %s." % section_id)

        return True
    
    def _load_media_info_cache(self, section_id=None, rating_key=None):
        cache_time = None
        rows = []
        library_count = 0

        # Import media info cache from json file
        if rating_key:
            try:
                inFilePath = os.path.join(plexpy.CONFIG.CACHE_DIR,'media_info_%s-%s.json' % (section_id, rating_key))
                with open(inFilePath, 'r') as inFile:
                    data = json.load(inFile)
                    if isinstance(data, dict):
                        cache_time = data['last_refreshed']
                        rows = data['rows']
                    else:
                        rows = data
                    library_count = len(rows)
                logger.debug("Tautulli Libraries :: Loaded media info from cache for rating_key %s (%s items)." % (rating_key, library_count))
            except IOError as e:
                logger.debug("Tautulli Libraries :: No media info cache for rating_key %s." % rating_key)

        elif section_id:
            try:
                inFilePath = os.path.join(plexpy.CONFIG.CACHE_DIR,'media_info_%s.json' % section_id)
                with open(inFilePath, 'r') as inFile:
                    data = json.load(inFile)
                    if isinstance(data, dict):
                        cache_time = data['last_refreshed']
                        rows = data['rows']
                    else:
                        rows = data
                    library_count = len(rows)
                logger.debug("Tautulli Libraries :: Loaded media info from cache for section_id %s (%s items)." % (section_id, library_count))
            except IOError as e:
                logger.debug("Tautulli Libraries :: No media info cache for section_id %s." % section_id)

        return cache_time, rows, library_count
    
    def _save_media_info_cache(self, section_id=None, rating_key=None, rows=None):
        cache_time = helpers.timestamp()

        if rows is None:
            rows = []
        
        if rating_key:
            try:
                outFilePath = os.path.join(plexpy.CONFIG.CACHE_DIR,'media_info_%s-%s.json' % (section_id, rating_key))
                with open(outFilePath, 'w') as outFile:
                    json.dump({'last_refreshed': cache_time, 'rows': rows}, outFile)
                logger.debug("Tautulli Libraries :: Saved media info cache for rating_key %s." % rating_key)
            except IOError as e:
                logger.debug("Tautulli Libraries :: Unable to create cache file for rating_key %s." % rating_key)

        elif section_id:
            try:
                outFilePath = os.path.join(plexpy.CONFIG.CACHE_DIR,'media_info_%s.json' % section_id)
                with open(outFilePath, 'w') as outFile:
                    json.dump({'last_refreshed': cache_time, 'rows': rows}, outFile)
                logger.debug("Tautulli Libraries :: Saved media info cache for section_id %s." % section_id)
            except IOError as e:
                logger.debug("Tautulli Libraries :: Unable to create cache file for section_id %s." % section_id)

    def set_config(self, section_id=None, custom_thumb='', custom_art='',
                   do_notify=1, keep_history=1, do_notify_created=1):
        if section_id:
            monitor_db = database.MonitorDatabase()

            key_dict = {'section_id': section_id}
            value_dict = {'custom_thumb_url': custom_thumb,
                          'custom_art_url': custom_art,
                          'do_notify': do_notify,
                          'do_notify_created': do_notify_created,
                          'keep_history': keep_history}
            try:
                monitor_db.upsert('library_sections', value_dict, key_dict)
            except Exception as e:
                logger.warn("Tautulli Libraries :: Unable to execute database query for set_config: %s." % e)

    def get_details(self, section_id=None, server_id=None, include_last_accessed=False):
        default_return = {'row_id': 0,
                          'server_id': '',
                          'section_id': 0,
                          'section_name': 'Local',
                          'section_type': '',
                          'library_thumb': common.DEFAULT_COVER_THUMB,
                          'library_art': '',
                          'count': 0,
                          'parent_count': 0,
                          'child_count': 0,
                          'is_active': 1,
                          'do_notify': 0,
                          'do_notify_created': 0,
                          'keep_history': 1,
                          'deleted_section': 0,
                          'last_accessed': None,
                          }

        if not section_id:
            return default_return

        if server_id is None:
            server_id = plexpy.CONFIG.PMS_IDENTIFIER

        library_details = self.get_library_details(section_id=section_id, server_id=server_id,
                                                   include_last_accessed=include_last_accessed)

        if library_details:
            return library_details

        else:
            logger.warn("Tautulli Libraries :: Unable to retrieve library %s from database. Requesting library list refresh."
                        % section_id)
            # Let's first refresh the libraries list to make sure the library isn't newly added and not in the db yet
            refresh_libraries()

            library_details = self.get_library_details(section_id=section_id, server_id=server_id,
                                                       include_last_accessed=include_last_accessed)

            if library_details:
                return library_details

            else:
                logger.warn("Tautulli Users :: Unable to retrieve library %s from database. Returning 'Local' library."
                            % section_id)
                # If there is no library data we must return something
                return default_return

    def get_library_details(self, section_id=None, server_id=None, include_last_accessed=False):
        if server_id is None:
            server_id = plexpy.CONFIG.PMS_IDENTIFIER

        last_accessed = 'NULL'
        join = ''
        if include_last_accessed:
            last_accessed = "MAX(session_history.started)"
            join = "LEFT OUTER JOIN session_history ON library_sections.section_id = session_history.section_id " \

        monitor_db = database.MonitorDatabase()

        try:
            if str(section_id).isdigit():
                where = "library_sections.section_id = ?"
                args = [section_id]
            else:
                raise Exception('Missing section_id')

            query = "SELECT library_sections.id AS row_id, server_id, library_sections.section_id, " \
                    "section_name, section_type, " \
                    "count, parent_count, child_count, " \
                    "library_sections.thumb AS library_thumb, custom_thumb_url AS custom_thumb, " \
                    "library_sections.art AS library_art, " \
                    "custom_art_url AS custom_art, is_active, " \
                    "do_notify, do_notify_created, keep_history, deleted_section, %s AS last_accessed " \
                    "FROM library_sections %s " \
                    "WHERE %s AND server_id = ? " % (last_accessed, join, where)
            result = monitor_db.select(query, args=args + [server_id])
        except Exception as e:
            logger.warn("Tautulli Libraries :: Unable to execute database query for get_library_details: %s." % e)
            result = []

        library_details = {}
        if result:
            for item in result:
                if item['custom_thumb'] and item['custom_thumb'] != item['library_thumb']:
                    library_thumb = item['custom_thumb']
                elif item['library_thumb']:
                    library_thumb = item['library_thumb']
                else:
                    library_thumb = common.DEFAULT_COVER_THUMB

                if item['custom_art'] and item['custom_art'] != item['library_art']:
                    library_art = item['custom_art']
                else:
                    library_art = item['library_art']

                library_details = {'row_id': item['row_id'],
                                   'server_id': item['server_id'],
                                   'section_id': item['section_id'],
                                   'section_name': item['section_name'],
                                   'section_type': item['section_type'],
                                   'library_thumb': library_thumb,
                                   'library_art': library_art,
                                   'count': item['count'],
                                   'parent_count': item['parent_count'],
                                   'child_count': item['child_count'],
                                   'is_active': item['is_active'],
                                   'do_notify': item['do_notify'],
                                   'do_notify_created': item['do_notify_created'],
                                   'keep_history': item['keep_history'],
                                   'deleted_section': item['deleted_section'],
                                   'last_accessed': item['last_accessed']
                                   }
        return library_details

    def get_watch_time_stats(self, section_id=None, grouping=None, query_days=None):
        if not session.allow_session_library(section_id):
            return []

        if grouping is None:
            grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES

        if query_days and query_days is not None:
            query_days = map(helpers.cast_to_int, str(query_days).split(','))
        else:
            query_days = [1, 7, 30, 0]

        timestamp = helpers.timestamp()

        monitor_db = database.MonitorDatabase()

        library_watch_time_stats = []

        group_by = 'session_history.reference_id' if grouping else 'session_history.id'

        for days in query_days:
            timestamp_query = timestamp - days * 24 * 60 * 60

            try:
                if days > 0:
                    if str(section_id).isdigit():
                        query = "SELECT (SUM(stopped - started) - " \
                                "SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END)) AS total_time, " \
                                "COUNT(DISTINCT %s) AS total_plays " \
                                "FROM session_history " \
                                "JOIN session_history_metadata ON session_history_metadata.id = session_history.id " \
                                "WHERE stopped >= %s " \
                                "AND section_id = ?" % (group_by, timestamp_query)
                        result = monitor_db.select(query, args=[section_id])
                    else:
                        result = []
                else:
                    if str(section_id).isdigit():
                        query = "SELECT (SUM(stopped - started) - " \
                                "SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END)) AS total_time, " \
                                "COUNT(DISTINCT %s) AS total_plays " \
                                "FROM session_history " \
                                "JOIN session_history_metadata ON session_history_metadata.id = session_history.id " \
                                "WHERE section_id = ?" % group_by
                        result = monitor_db.select(query, args=[section_id])
                    else:
                        result = []
            except Exception as e:
                logger.warn("Tautulli Libraries :: Unable to execute database query for get_watch_time_stats: %s." % e)
                result = []

            for item in result:
                if item['total_time']:
                    total_time = item['total_time']
                    total_plays = item['total_plays']
                else:
                    total_time = 0
                    total_plays = 0

                row = {'query_days': days,
                       'total_time': total_time,
                       'total_plays': total_plays
                       }

                library_watch_time_stats.append(row)

        return library_watch_time_stats

    def get_user_stats(self, section_id=None, grouping=None):
        if not session.allow_session_library(section_id):
            return []

        if grouping is None:
            grouping = plexpy.CONFIG.GROUP_HISTORY_TABLES

        monitor_db = database.MonitorDatabase()

        user_stats = []

        group_by = 'session_history.reference_id' if grouping else 'session_history.id'

        try:
            if str(section_id).isdigit():
                query = "SELECT (CASE WHEN users.friendly_name IS NULL OR TRIM(users.friendly_name) = '' " \
                        "THEN users.username ELSE users.friendly_name END) AS friendly_name, " \
                        "users.user_id, users.username, users.thumb, users.custom_avatar_url AS custom_thumb, " \
                        "COUNT(DISTINCT %s) AS total_plays, (SUM(stopped - started) - " \
                        "SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END)) AS total_time " \
                        "FROM session_history " \
                        "JOIN session_history_metadata ON session_history_metadata.id = session_history.id " \
                        "JOIN users ON users.user_id = session_history.user_id " \
                        "WHERE section_id = ? " \
                        "GROUP BY users.user_id " \
                        "ORDER BY total_plays DESC, total_time DESC" % group_by
                result = monitor_db.select(query, args=[section_id])
            else:
                result = []
        except Exception as e:
            logger.warn("Tautulli Libraries :: Unable to execute database query for get_user_stats: %s." % e)
            result = []

        for item in result:
            if item['custom_thumb'] and item['custom_thumb'] != item['thumb']:
                user_thumb = item['custom_thumb']
            elif item['thumb']:
                user_thumb = item['thumb']
            else:
                user_thumb = common.DEFAULT_USER_THUMB

            row = {'friendly_name': item['friendly_name'],
                   'user_id': item['user_id'],
                   'user_thumb': user_thumb,
                   'username': item['username'],
                   'total_plays': item['total_plays'],
                   'total_time': item['total_time']
                   }
            user_stats.append(row)

        return session.mask_session_info(user_stats, mask_metadata=False)

    def get_recently_watched(self, section_id=None, limit='10'):
        if not session.allow_session_library(section_id):
            return []

        monitor_db = database.MonitorDatabase()
        recently_watched = []

        if not limit.isdigit():
            limit = '10'

        try:
            if str(section_id).isdigit():
                query = "SELECT session_history.id, session_history.media_type, guid, " \
                        "session_history.rating_key, session_history.parent_rating_key, session_history.grandparent_rating_key, " \
                        "title, parent_title, grandparent_title, original_title, " \
                        "thumb, parent_thumb, grandparent_thumb, media_index, parent_media_index, " \
                        "year, originally_available_at, added_at, live, started, user, content_rating, labels, section_id " \
                        "FROM session_history_metadata " \
                        "JOIN session_history ON session_history_metadata.id = session_history.id " \
                        "WHERE section_id = ? " \
                        "GROUP BY session_history.rating_key " \
                        "ORDER BY MAX(started) DESC LIMIT ?"
                result = monitor_db.select(query, args=[section_id, limit])
            else:
                result = []
        except Exception as e:
            logger.warn("Tautulli Libraries :: Unable to execute database query for get_recently_watched: %s." % e)
            result = []

        for row in result:
                if row['media_type'] == 'episode' and row['parent_thumb']:
                    thumb = row['parent_thumb']
                elif row['media_type'] == 'episode':
                    thumb = row['grandparent_thumb']
                else:
                    thumb = row['thumb']

                recent_output = {'row_id': row['id'],
                                 'media_type': row['media_type'],
                                 'rating_key': row['rating_key'],
                                 'parent_rating_key': row['parent_rating_key'],
                                 'grandparent_rating_key': row['grandparent_rating_key'],
                                 'title': row['title'],
                                 'parent_title': row['parent_title'],
                                 'grandparent_title': row['grandparent_title'],
                                 'original_title': row['original_title'],
                                 'thumb': thumb,
                                 'media_index': row['media_index'],
                                 'parent_media_index': row['parent_media_index'],
                                 'year': row['year'],
                                 'originally_available_at': row['originally_available_at'],
                                 'live': row['live'],
                                 'guid': row['guid'],
                                 'time': row['started'],
                                 'user': row['user'],
                                 'section_id': row['section_id'],
                                 'content_rating': row['content_rating'],
                                 'labels': row['labels'].split(';') if row['labels'] else (),
                                 }
                recently_watched.append(recent_output)

        return session.mask_session_info(recently_watched)

    def get_sections(self):
        monitor_db = database.MonitorDatabase()

        try:
            query = "SELECT section_id, section_name, section_type, agent " \
                    "FROM library_sections WHERE deleted_section = 0"
            result = monitor_db.select(query=query)
        except Exception as e:
            logger.warn("Tautulli Libraries :: Unable to execute database query for get_sections: %s." % e)
            return None

        libraries = []
        for item in result:
            library = {'section_id': item['section_id'],
                       'section_name': item['section_name'],
                       'section_type': item['section_type'],
                       'agent': item['agent']
                       }
            libraries.append(library)

        return libraries

    def delete(self, server_id=None, section_id=None, row_ids=None, purge_only=False):
        monitor_db = database.MonitorDatabase()

        if row_ids and row_ids is not None:
            row_ids = list(map(helpers.cast_to_int, row_ids.split(',')))

            # Get the section_ids corresponding to the row_ids
            result = monitor_db.select("SELECT server_id, section_id FROM library_sections "
                                       "WHERE id IN ({})".format(",".join(["?"] * len(row_ids))), row_ids)

            success = []
            for library in result:
                success.append(self.delete(server_id=library['server_id'], section_id=library['section_id'],
                                           purge_only=purge_only))
            return all(success)

        elif str(section_id).isdigit():
            server_id = server_id or plexpy.CONFIG.PMS_IDENTIFIER
            if server_id == plexpy.CONFIG.PMS_IDENTIFIER:
                delete_success = database.delete_library_history(section_id=section_id)
            else:
                logger.warn("Tautulli Libraries :: Library history not deleted for library section_id %s "
                            "because library server_id %s does not match Plex server identifier %s."
                            % (section_id, server_id, plexpy.CONFIG.PMS_IDENTIFIER))
                delete_success = True

            if purge_only:
                return delete_success
            else:
                logger.info("Tautulli Libraries :: Deleting library with server_id %s and section_id %s from database."
                            % (server_id, section_id))
                try:
                    monitor_db.action("UPDATE library_sections "
                                      "SET deleted_section = 1, keep_history = 0, do_notify = 0, do_notify_created = 0 "
                                      "WHERE server_id = ? AND section_id = ?", [server_id, section_id])
                    return delete_success
                except Exception as e:
                    logger.warn("Tautulli Libraries :: Unable to execute database query for delete: %s." % e)

        else:
            return False

    def undelete(self, section_id=None, section_name=None):
        monitor_db = database.MonitorDatabase()

        try:
            if section_id and section_id.isdigit():
                query = "SELECT * FROM library_sections WHERE section_id = ?"
                result = monitor_db.select(query=query, args=[section_id])
                if result:
                    logger.info("Tautulli Libraries :: Re-adding library with id %s to database." % section_id)
                    monitor_db.action("UPDATE library_sections "
                                      "SET deleted_section = 0, keep_history = 1, do_notify = 1, do_notify_created = 1 "
                                      "WHERE section_id = ?",
                                      [section_id])
                    return True
                else:
                    return False

            elif section_name:
                query = "SELECT * FROM library_sections WHERE section_name = ?"
                result = monitor_db.select(query=query, args=[section_name])
                if result:
                    logger.info("Tautulli Libraries :: Re-adding library with name %s to database." % section_name)
                    monitor_db.action("UPDATE library_sections "
                                      "SET deleted_section = 0, keep_history = 1, do_notify = 1, do_notify_created = 1 "
                                      "WHERE section_name = ?",
                                      [section_name])
                    return True
                else:
                    return False

        except Exception as e:
            logger.warn("Tautulli Libraries :: Unable to execute database query for undelete: %s." % e)

    def delete_media_info_cache(self, section_id=None):
        import os

        try:
            if section_id.isdigit():
                [os.remove(os.path.join(plexpy.CONFIG.CACHE_DIR, f)) for f in os.listdir(plexpy.CONFIG.CACHE_DIR)
                 if f.startswith('media_info_%s' % section_id) and f.endswith('.json')]

                logger.debug("Tautulli Libraries :: Deleted media info table cache for section_id %s." % section_id)
                return 'Deleted media info table cache for library with id %s.' % section_id
            else:
                return 'Unable to delete media info table cache, section_id not valid.'
        except Exception as e:
            logger.warn("Tautulli Libraries :: Unable to delete media info table cache: %s." % e)

    def delete_duplicate_libraries(self):
        monitor_db = database.MonitorDatabase()

        # Refresh the PMS_URL to make sure the server_id is updated
        plextv.get_server_resources()

        server_id = plexpy.CONFIG.PMS_IDENTIFIER

        try:
            logger.debug("Tautulli Libraries :: Deleting libraries where server_id does not match %s." % server_id)
            monitor_db.action("DELETE FROM library_sections WHERE server_id != ?", [server_id])

            return 'Deleted duplicate libraries from the database.'
        except Exception as e:
            logger.warn("Tautulli Libraries :: Unable to delete duplicate libraries: %s." % e)
