# -*- coding: utf-8 -*-
# plugin.video.alkoflix
# resources/lib/modules/myservices.py

import time
import requests
from threading import Thread, Timer
from windows import create_window
from modules import kodi_utils, cache
# logger = kodi_utils.logger

quote, clear_cache = requests.utils.quote, cache.clear_cache
get_setting, set_setting, sleep = kodi_utils.get_setting, kodi_utils.set_setting, kodi_utils.sleep
notification, confirm_dialog = kodi_utils.notification, kodi_utils.confirm_dialog
user_agent = 'alkoFlix/%s' % kodi_utils.get_addoninfo('version')
qr_str = 'https://api.qrserver.com/v1/create-qr-code/?size=256x256&qzone=1%s'
meta_keys = 'title year poster fanart clearlogo tmdblogo'
code_str, nav2_str, await_str = 'CODE PIN : [B]%s[/B]', 'ADRESSE : [B]%s[/B]', 'TEMPS RESTANT : [B]%02d:%02d[/B]'
auth_str, noauth_str = 'Autorisé : sélectionner pour supprimer', 'Non autorisé : sélectionner pour ajouter'
removed_auth_str, set_auth_str = 'Autorisation %s supprimée', 'Autorisation %s activée'
timeout = 10.05

def watch_indicators(function):
	def wrapper(instance, *args, **kwargs):
		result = function(instance, *args, **kwargs)
		if result == 'authorized':
			kodi_utils.ok_dialog(text=(
				'Service connecté.[CR][CR]'
				'Pensez à confirmer votre fournisseur de statut de visionnage dans les paramètres alkoFlix.'
			))
		return result
	return wrapper


def _sync_provider_to_local_before_disconnect(watched_indicator, service_name):
	try:
		from caches.watched_cache import sync_watched_provider_to_local
		result = sync_watched_provider_to_local(watched_indicator)
		kodi_utils.logger('watched provider disconnect', '%s -> local | watched=%s | progress=%s' % (service_name, result.get('watched', 0), result.get('progress', 0)))
		return result
	except Exception as e:
		try: kodi_utils.logger('watched provider disconnect', '%s -> local FAILED | %s: %s' % (service_name, type(e).__name__, e))
		except: pass
		return {'watched': 0, 'progress': 0}


def _disconnect_ratings_provider(provider, service_name):
	# Les notes utilisateur sont séparées des statuts de visionnage.
	# Si le service déconnecté était le fournisseur de notation actif, on désactive
	# le fournisseur au lieu de laisser alkoFlix tenter des requêtes fantômes.
	try:
		from modules import user_ratings
		user_ratings.clear_user_ratings_cache(provider)
	except Exception:
		pass
	try:
		current = int(get_setting('ratings.provider', '3'))
	except Exception:
		current = 3
	if current == provider:
		set_setting('ratings.provider', '3')
		try: kodi_utils.logger('ratings provider disconnect', '%s -> disabled' % service_name)
		except Exception: pass

def _make_progress_dialog(**kwargs):
	progress_dialog = create_window(('windows.sources', 'ProgressMedia'), 'progress_media.xml', **kwargs)
	Thread(target=progress_dialog.run).start()
	return progress_dialog

def authorize():
	def _builder():
		for name, api in services:
			item = kodi_utils.make_listitem()
			item.setLabel('[B]%s[/B]' % name.upper())
			item.setLabel2(auth_str if api().token else noauth_str)
			item.setArt({'icon': '%s%s' % (icon_path, api.icon)})
			yield(item)
	icon_path, services = kodi_utils.media_path(), (
		('alkopastes', AlkoPastes), ('trakt', Trakt), ('mdblist', MDBList), ('tmdblist', TMDbList),
		('alldebrid', AllDebrid), ('real-debrid', RealDebrid), ('torbox', TorBox)
	)
	service = kodi_utils.dialog.select('Me connecter', list(_builder()), useDetails=True)
	if service < 0: return
	try: success = services[service][1]().set()
	except Exception as e: kodi_utils.logger('myservices error', str(e))
	else: return success
	return notification(32574)

class RepeatTimer(Timer):
	def run(self):
		while not self.finished.wait(self.interval):
			self.function(*self.args, **self.kwargs)

class RealDebrid:
	icon = 'realdebrid.png'
	def __init__(self):
		self.token = get_setting('rd.token')
		self.client_id = get_setting('rd.client_id') or 'X245A4XAIBGVM'
		self.secret = get_setting('rd.secret')

	def base_url(self, path):
		return 'https://app.real-debrid.com/%s' % path

	def poll_auth(self, data):
		params = {'client_id': self.client_id, 'code': data['code']}
		response = requests.get(self.base_url('oauth/v2/device/credentials'), params=params, timeout=timeout)
		if not response.ok: return
		data.update(response.json())
		self.secret = data['client_secret']

	def set(self):
		cls_name = 'Real Debrid'
		if self.token:
			if not confirm_dialog(): return
			set_setting('rd.username', '')
			set_setting('rd.client_id', '')
			set_setting('rd.token', '')
			set_setting('rd.refresh', '')
			set_setting('rd.secret', '')
			clear_cache('rd_cloud', silent=True)
			return notification(removed_auth_str % cls_name)

		params = {'client_id': self.client_id, 'new_credentials': 'yes'}
		response = requests.get(self.base_url('oauth/v2/device/code'), params=params, timeout=timeout)
		result = response.json()
		data = {'code': result['device_code'], 'grant_type': 'http://oauth.net/grant_type/device/1.0'}
		expires_in, expires_at = result['expires_in'], result['expires_in'] + time.monotonic()
		try: qr_icon = qr_str % '&data=%s' % quote(result['direct_verification_url'])
		except: qr_icon = ''
		meta = {**dict.fromkeys(meta_keys.split(), ''), 'poster': qr_icon}
		detail = code_str % result['user_code'], nav2_str % result['verification_url']
		progress_dialog = _make_progress_dialog(meta=meta)
		timer = RepeatTimer(result['interval'], self.poll_auth, args=(data,))
		timer.start()
		for i in range(1, expires_in + 1):
			if self.secret or progress_dialog.iscanceled(): break
			lines = await_str % divmod(expires_at - time.monotonic(), 60), *detail
			progress = 100 - int(100 * i / expires_in)
			progress_dialog.update('[CR]'.join(lines), progress)
			sleep(1000)
		timer.cancel()
		progress_dialog.close()
		if progress_dialog.iscanceled(): return False
		if not self.secret: return notification(32574)
		response = requests.post(self.base_url('oauth/v2/token'), data=data, timeout=timeout)
		data.update(response.json())
		sleep(500)
		headers = {'Authorization': 'Bearer %s' % data['access_token']}
		response = requests.get(self.base_url('rest/1.0/user'), headers=headers, timeout=timeout)
		username = response.json()['username']
		client_id, secret = data['client_id'], data['client_secret']
		token, refresh = data['access_token'], data['refresh_token']
		set_setting('rd.username', str(username))
		set_setting('rd.client_id', client_id)
		set_setting('rd.token', token)
		set_setting('rd.refresh', refresh)
		set_setting('rd.secret', secret)
		set_setting('rd.enabled', 'true')
		set_setting('rd.torrent.enabled', 'true')
		notification(set_auth_str % cls_name)
		return True

class AllDebrid:
	icon = 'alldebrid-color.png'
	def __init__(self):
		self.token = get_setting('ad.token')

	def base_url(self, path):
		return 'https://api.alldebrid.com/%s' % path

	def poll_auth(self, url):
		response = requests.get(url, timeout=timeout)
		result = response.json()['data']
		self.token = result.get('apikey', '')

	def set(self):
		cls_name = self.__class__.__name__
		if self.token:
			if not confirm_dialog(): return
			set_setting('ad.account_id', '')
			set_setting('ad.token', '')
			clear_cache('ad_cloud', silent=True)
			return notification(removed_auth_str % cls_name)


		response = requests.get(self.base_url('v4/pin/get'), timeout=timeout)
		result = response.json()['data']
		expires_in, expires_at = result['expires_in'], result['expires_in'] + time.monotonic()
		verification_url = result.get('user_url') or result.get('base_url') or ''
		try: qr_icon = qr_str % '&bgcolor=ffd700&data=%s' % quote(verification_url)
		except: qr_icon = ''
		meta = {**dict.fromkeys(meta_keys.split(), ''), 'poster': qr_icon}
		detail = code_str % result['pin'], nav2_str % verification_url
		progress_dialog = _make_progress_dialog(meta=meta)
		timer = RepeatTimer(5, self.poll_auth, args=(result['check_url'],))
		timer.start()
		for i in range(1, expires_in + 1):
			if self.token or progress_dialog.iscanceled(): break
			lines = await_str % divmod(expires_at - time.monotonic(), 60), *detail
			progress = 100 - int(100 * i / expires_in)
			progress_dialog.update('[CR]'.join(lines), progress)
			sleep(1000)
		timer.cancel()
		progress_dialog.close()
		if progress_dialog.iscanceled(): return False
		if not self.token: return notification(32574)
		sleep(500)
		headers = {'Authorization': 'Bearer %s' % self.token}
		response = requests.get(self.base_url('v4/user'), headers=headers, timeout=timeout)
		result = response.json()['data']
		username = result['user']['username']
		set_setting('ad.account_id', str(username))
		set_setting('ad.token', self.token)
		set_setting('ad.enabled', 'true')
		set_setting('ad.torrent.enabled', 'true')
		notification(set_auth_str % cls_name)
		return True

class TorBox:
	icon = 'torbox.png'
	def __init__(self):
		self.token = get_setting('tb.token')

	def base_url(self, path):
		return 'https://api.torbox.app/v1/api/%s' % path

	def poll_auth(self, data):
		response = requests.post(self.base_url('user/auth/device/token'), json=data, timeout=timeout)
		if not response.ok: return
		data.update(response.json())
		self.token = data

	def set(self):
		cls_name = self.__class__.__name__
		if self.token:
			if not confirm_dialog(): return
			set_setting('tb.token', '')
			set_setting('tb.account_id', '')
			clear_cache('tb_cloud', silent=True)
			return notification(removed_auth_str % cls_name)

		params = {'app': user_agent}
		response = requests.get(self.base_url('user/auth/device/start'), params=params, timeout=timeout)
		result = response.json()['data']
		data = {'device_code': result['device_code']}
		expires_in, expires_at = 600, 600 + time.monotonic()
		try: qr_icon = qr_str % '&bgcolor=04bf8a&data=%s' % quote(result['verification_url'])
		except: qr_icon = ''
		meta = {**dict.fromkeys(meta_keys.split(), ''), 'poster': qr_icon}
		detail = code_str % result['code'], nav2_str % result['friendly_verification_url']
		progress_dialog = _make_progress_dialog(meta=meta)
		timer = RepeatTimer(result['interval'], self.poll_auth, args=(data,))
		timer.start()
		for i in range(1, expires_in + 1):
			if self.token or progress_dialog.iscanceled(): break
			lines = await_str % divmod(expires_at - time.monotonic(), 60), *detail
			progress = 100 - int(100 * i / expires_in)
			progress_dialog.update('[CR]'.join(lines), progress)
			sleep(1000)
		timer.cancel()
		progress_dialog.close()
		if progress_dialog.iscanceled(): return False
		self.token = data['data']['access_token']
		headers = {'Authorization': 'Bearer %s' % self.token}
		response = requests.get(self.base_url('user/me'), headers=headers, timeout=timeout)
		result = response.json()
		customer = result['data']['customer']
		set_setting('tb.account_id', str(customer))
		set_setting('tb.token', self.token)
		set_setting('tb.enabled', 'true')
		set_setting('tb.torrent.enabled', 'true')
		notification(set_auth_str % cls_name)
		return True

class EasyNews:
	icon = 'easynews.png'
	def __init__(self):
		username = get_setting('easynews_user')
		password = get_setting('easynews_password')
		self.token = all((username, password))

	def set(self):
		from debrids.easynews_api import EasyNewsAPI, clear_media_results_database
		cls_name = self.__class__.__name__
		if self.token:
			if not confirm_dialog(): return
			set_setting('easynews_user', '')
			set_setting('easynews_password', '')
			set_setting('provider.easynews', 'false')
			clear_media_results_database()
			return notification(removed_auth_str % cls_name)

		username = kodi_utils.dialog.input("Nom d'utilisateur EasyNews :")
		password = kodi_utils.dialog.input('Mot de passe EasyNews :')
		if not all((username, password)): return
		api = EasyNewsAPI()
		api.username, api.password = username, password
		account_info, usage_info = api.account()
		if not account_info or not usage_info: return notification(32574)
		set_setting('easynews_user', username)
		set_setting('easynews_password', password)
		set_setting('provider.easynews', 'true')
		notification(set_auth_str % cls_name)
		return True

class Trakt:
	icon = 'trakt-color.png'
	def __init__(self):
		self.token = get_setting('trakt.token')
		self.client_id = get_setting('trakt.client_id')
		self.secret = get_setting('trakt.client_secret')

	def base_url(self, path):
		return 'https://api.trakt.tv/%s' % path

	def poll_auth(self, data):
		response = requests.post(self.base_url('oauth/device/token'), json=data, timeout=timeout)
		if not response.ok: return
		data.update(response.json())
		self.token = data['access_token']

	@watch_indicators
	def set(self):
		cls_name = self.__class__.__name__
		if self.token:
			if not confirm_dialog(): return
			_sync_provider_to_local_before_disconnect(1, cls_name)
			data = {'token': self.token, 'client_id': self.client_id, 'client_secret': self.secret}
			response = requests.post(self.base_url('oauth/revoke'), json=data, timeout=timeout)
			set_setting('trakt_user', '')
			set_setting('trakt.token', '')
			set_setting('trakt.refresh', '')
			set_setting('trakt.expires', '')
			set_setting('trakt_indicators_active', 'false')
			if get_setting('watched_indicators') == '1': set_setting('watched_indicators', '0')
			_disconnect_ratings_provider(0, cls_name)
			sleep(500)
			try:
				from caches.trakt_cache import clear_all_trakt_cache_data
				clear_all_trakt_cache_data(refresh=False, reason='disconnect')
			except Exception: pass
			return notification(removed_auth_str % cls_name)

		data = {'client_id': self.client_id, 'client_secret': self.secret, 'code': ''}
		response = requests.post(self.base_url('oauth/device/code'), json=data, timeout=timeout)
		result = response.json()
		data['code'] = result['device_code']
		expires_in, expires_at = result['expires_in'], result['expires_in'] + time.monotonic()
		try: qr_icon = qr_str % '&color=f00&data=%s' % quote('%s/%s' % (result['verification_url'], result['user_code']))
		except: qr_icon = ''
		meta = {**dict.fromkeys(meta_keys.split(), ''), 'poster': qr_icon}
		detail = code_str % result['user_code'], nav2_str % result['verification_url']
		progress_dialog = _make_progress_dialog(meta=meta)
		timer = RepeatTimer(result['interval'], self.poll_auth, args=(data,))
		timer.start()
		for i in range(1, expires_in + 1):
			if self.token or progress_dialog.iscanceled(): break
			lines = await_str % divmod(expires_at - time.monotonic(), 60), *detail
			progress = 100 - int(100 * i / expires_in)
			progress_dialog.update('[CR]'.join(lines), progress)
			sleep(1000)
		timer.cancel()
		progress_dialog.close()
		if progress_dialog.iscanceled(): return False
		if not self.token: return notification(32574)
		sleep(500)
		headers = {'trakt-api-key': self.client_id, 'trakt-api-version': '2', 'Content-Type': 'application/json'}
		headers.update({'Authorization': 'Bearer %s' % self.token})
		response = requests.get(self.base_url('users/me'), headers=headers, timeout=timeout)
		username = response.json()['username']
		expires = int(data['created_at']) + int(data['expires_in'])
		refresh, token = data['refresh_token'], data['access_token']
		set_setting('trakt_user', str(username))
		set_setting('trakt.token', token)
		set_setting('trakt.refresh', refresh)
		set_setting('trakt.expires', str(expires))
		set_setting('trakt_indicators_active', 'true')
		notification(set_auth_str % cls_name)
		sleep(500)
		clear_cache('trakt', silent=True)
		return 'authorized'

class AlkoPastes:
	icon = 'alkoflix.png'
	def __init__(self):
		self.token = get_setting('alkopastes.api_key_user')

	def base_url(self):
		return 'https://paste.lesalkodiques.com/api.php'

	def set(self):
		cls_name = 'alkoPastes'
		if self.token:
			if not confirm_dialog(): return
			set_setting('alkopastes_user', '')
			set_setting('alkopastes.api_key_user', '')
			set_setting('alkopastes.api_mode', '0')
			return notification(removed_auth_str % cls_name)

		api_key = kodi_utils.dialog.input('Clé API alkoPastes :')
		if not api_key: return
		try:
			response = requests.get(self.base_url(), params={'action': 'me'}, headers={'X-API-Key': api_key, 'User-Agent': user_agent}, timeout=timeout)
			result = response.json()
		except Exception as e:
			kodi_utils.logger('alkoPastes auth error', '%s: %s' % (type(e).__name__, e))
			return notification(32574)
		if not response.ok or not result.get('success'):
			return notification(32574)
		user = result.get('user') or result.get('account') or {}
		username = user.get('username') or user.get('name') or result.get('username') or 'Connecté'
		set_setting('alkopastes_user', str(username))
		set_setting('alkopastes.api_key_user', api_key)
		set_setting('alkopastes.api_mode', '1')
		notification(set_auth_str % cls_name)
		return True

class MDBList:
	icon = 'mdblist-color.png'
	client_id_default = 'EUk8hb6sCGab70Z08k9EKMv1kahOh311Xxk4fDrj'
	device_endpoints = ('oauth/device-authorization/',)
	token_endpoints = ('oauth/token/', 'oauth/token')

	def __init__(self):
		self.api_key = get_setting('mdblist.token')
		self.access_token = get_setting('mdblist.access_token')
		self.refresh_token = get_setting('mdblist.refresh_token')
		self.client_id = get_setting('mdblist.client_id') or self.client_id_default
		self.token = self.access_token or self.api_key

	def base_url(self, path):
		return 'https://api.mdblist.com/%s' % path.lstrip('/')

	def _json_response(self, response):
		try: return response.json()
		except Exception: return {}

	def _clear_account(self):
		_sync_provider_to_local_before_disconnect(2, 'MDBList')
		for setting_id in (
			'mdblist_user', 'mdblist.token', 'mdblist.auth_type', 'mdblist.access_token',
			'mdblist.refresh_token', 'mdblist.expires'
		):
			set_setting(setting_id, '')
		set_setting('mdbl_indicators_active', 'false')
		if get_setting('watched_indicators') == '2': set_setting('watched_indicators', '0')
		_disconnect_ratings_provider(1, 'MDBList')
		self.api_key = self.access_token = self.refresh_token = self.token = ''

	def _activate_account(self, username, auth_type, api_key='', access_token='', refresh_token='', expires=''):
		set_setting('mdblist_user', str(username or 'Connecté'))
		set_setting('mdblist.auth_type', auth_type)
		set_setting('mdblist.token', api_key or '')
		set_setting('mdblist.access_token', access_token or '')
		set_setting('mdblist.refresh_token', refresh_token or '')
		set_setting('mdblist.expires', str(expires or ''))
		set_setting('mdblist.client_id', self.client_id)
		set_setting('mdbl_indicators_active', 'true')
		self.api_key, self.access_token, self.refresh_token = api_key, access_token, refresh_token
		self.token = access_token or api_key

	def _username_from_user_result(self, result):
		if not isinstance(result, dict): return None
		return (
			result.get('username') or result.get('name') or result.get('user_id')
			or result.get('id') or result.get('email') or 'Connecté'
		)

	def _user_from_api_key(self, api_key):
		params = {'apikey': api_key}
		response = requests.get(self.base_url('user'), params=params, timeout=timeout)
		result = self._json_response(response)
		if not response.ok: return None
		return self._username_from_user_result(result)

	def _user_from_access_token(self, access_token):
		headers = {'Authorization': 'Bearer %s' % access_token}
		response = requests.get(self.base_url('user'), headers=headers, timeout=timeout)
		result = self._json_response(response)
		if not response.ok: return None
		return self._username_from_user_result(result)

	def _request_device_code(self):
		payload = {'client_id': self.client_id, 'scope': 'write'}
		for endpoint in self.device_endpoints:
			try:
				response = requests.post(self.base_url(endpoint), data=payload, timeout=timeout)
				result = self._json_response(response)
			except Exception as e:
				kodi_utils.logger('MDBList OAuth device start failed', '%s: %s' % (type(e).__name__, e))
				continue
			if response.ok and result.get('device_code'):
				return result
			error = ''
			if isinstance(result, dict):
				error = result.get('error_description') or result.get('error') or ''
			kodi_utils.logger('MDBList OAuth device start failed', 'HTTP %s %s' % (getattr(response, 'status_code', ''), error))
		return None

	def _exchange_device_code(self, device_code):
		payload = {
			'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
			'device_code': device_code,
			'client_id': self.client_id
		}
		for endpoint in self.token_endpoints:
			try:
				response = requests.post(self.base_url(endpoint), data=payload, timeout=timeout)
				result = self._json_response(response)
			except Exception as e:
				kodi_utils.logger('MDBList OAuth token poll failed', '%s: %s' % (type(e).__name__, e))
				continue
			if response.ok and result.get('access_token'):
				return result
			error = result.get('error') if isinstance(result, dict) else None
			if error in ('authorization_pending', 'slow_down'):
				return False
		return False

	def poll_auth(self, data):
		if self.access_token: return
		result = self._exchange_device_code(data['device_code'])
		if not result: return
		data.update(result)
		self.access_token = result.get('access_token')
		self.refresh_token = result.get('refresh_token')

	def _set_api_key(self, cls_name):
		api_key = kodi_utils.dialog.input('Clé API MDBList :')
		if not api_key: return
		try: username = self._user_from_api_key(api_key)
		except Exception as e:
			kodi_utils.logger('MDBList API key auth failed', '%s: %s' % (type(e).__name__, e))
			return notification(32574)
		if not username: return notification(32574)
		self._activate_account(username, 'api', api_key=api_key)
		notification(set_auth_str % cls_name)
		sleep(500)
		clear_cache('mdblist', silent=True)
		return 'authorized'

	def _build_complete_verification_url(self, verification_url, user_code, result):
		complete_url = (
			result.get('verification_uri_complete')
			or result.get('verification_url_complete')
			or result.get('direct_verification_url')
			or result.get('user_url')
		)
		if complete_url: return complete_url
		if not user_code: return verification_url
		# MDBList ne retourne pas toujours une URL complète comme Trakt/Real-Debrid.
		# Le QR utilise donc l'URL de validation avec le code injecté dans la requête.
		separator = '&' if '?' in verification_url else '?'
		encoded_code = quote(user_code)
		return '%s%suser_code=%s&code=%s' % (verification_url, separator, encoded_code, encoded_code)

	def _set_oauth(self, cls_name):
		result = self._request_device_code()
		if not result: return notification(32574)
		data = {'device_code': result['device_code']}
		expires_in = int(result.get('expires_in') or 900)
		interval = max(2, int(result.get('interval') or 5))
		expires_at = expires_in + time.monotonic()
		verification_url = result.get('verification_uri') or result.get('verification_url') or result.get('user_url') or 'https://mdblist.com/activate'
		user_code = result.get('user_code') or result.get('code') or ''
		complete_url = self._build_complete_verification_url(verification_url, user_code, result)
		try: qr_icon = qr_str % '&data=%s' % quote(complete_url)
		except Exception: qr_icon = ''
		meta = {**dict.fromkeys(meta_keys.split(), ''), 'poster': qr_icon}
		detail = []
		if user_code: detail.append(code_str % user_code)
		detail.append(nav2_str % verification_url)
		progress_dialog = _make_progress_dialog(meta=meta)
		timer = RepeatTimer(interval, self.poll_auth, args=(data,))
		timer.start()
		for i in range(1, expires_in + 1):
			if self.access_token or progress_dialog.iscanceled(): break
			lines = await_str % divmod(expires_at - time.monotonic(), 60), *detail
			progress = 100 - int(100 * i / expires_in)
			progress_dialog.update('[CR]'.join(lines), progress)
			sleep(1000)
		timer.cancel()
		progress_dialog.close()
		if progress_dialog.iscanceled(): return False
		if not self.access_token: return notification(32574)
		try: username = self._user_from_access_token(self.access_token)
		except Exception as e:
			kodi_utils.logger('MDBList OAuth user lookup failed', '%s: %s' % (type(e).__name__, e))
			return notification(32574)
		if not username: return notification(32574)
		expires = int(time.time()) + int(data.get('expires_in') or 2592000)
		self._activate_account(
			username, 'oauth', access_token=self.access_token,
			refresh_token=data.get('refresh_token') or self.refresh_token or '', expires=expires
		)
		notification(set_auth_str % cls_name)
		sleep(500)
		clear_cache('mdblist', silent=True)
		return 'authorized'

	@watch_indicators
	def set(self):
		cls_name = self.__class__.__name__
		if self.token:
			if not confirm_dialog(): return
			self._clear_account()
			sleep(500)
			try:
				from indexers.mdblist_api import clear_mdbl_cache
				from caches.mdbl_cache import clear_all_mdbl_cache_data
				clear_mdbl_cache()
				clear_all_mdbl_cache_data(refresh=False)
			except Exception: pass
			return notification(removed_auth_str % cls_name)

		return self._set_oauth(cls_name)

class TMDbList:
	icon = 'tmdb.png'
	def __init__(self):
		self.read = get_setting('tmdb_read_token')
		self.token = get_setting('tmdb.token')
		self.headers = {'Authorization': 'Bearer %s' % self.read}

	def base_url(self, path):
		return 'https://api.themoviedb.org/%s' % path

	def poll_auth(self, data):
		response = requests.post(self.base_url('4/auth/access_token'), json=data, headers=self.headers, timeout=timeout)
		if not response.ok: return
		data.update(response.json())
		self.token = data['access_token']

	def set(self):
		cls_name = self.__class__.__name__
		if self.token:
			if not confirm_dialog(): return
			data = {'access_token': self.token}
			response = requests.delete(self.base_url('4/auth/access_token'), json=data, headers=self.headers, timeout=timeout)
			result = response.json()
			if not result['success']: return notification(32574)
			set_setting('tmdb.account_id', '')
			set_setting('tmdb.token', '')
			_disconnect_ratings_provider(2, cls_name)
			clear_cache('tmdblist', silent=True)
			notification(removed_auth_str % cls_name)
			session_id = get_setting('tmdb.session_id')
			data = {'session_id': session_id}
			response = requests.delete(self.base_url('3/authentication/session'), json=data, headers=self.headers, timeout=timeout)
			result = response.json()
			if not result['success']: return
			set_setting('tmdb.session_account_id', '')
			set_setting('tmdb.session_id', '')
			set_setting('tmdb.username', '')
			return

		response = requests.post(self.base_url('4/auth/request_token'), headers=self.headers, timeout=timeout)
		result = response.json()
		if not result['success']: return
		data = {'request_token': result['request_token']}
		expires_in, expires_at = 600, 600 + time.monotonic()
		url = 'https://www.themoviedb.org/auth/access?request_token=%s' % result['request_token']
		tiny_url = 'http://tinyurl.com/api-create.php'
		try: tiny_url = requests.get(tiny_url, params={'url': url}, timeout=timeout).text
		except: tiny_url = url
		qr_icon = qr_str % '&data=%s' % quote(tiny_url)
		kodi_utils.logger('tmdblist', '%s\n%s' % (tiny_url, url))
		meta = {**dict.fromkeys(meta_keys.split(), ''), 'poster': qr_icon}
		detail = nav2_str % tiny_url, ''
		progress_dialog = _make_progress_dialog(meta=meta)
		timer = RepeatTimer(5, self.poll_auth, args=(data,))
		timer.start()
		for i in range(1, expires_in + 1):
			if self.token or progress_dialog.iscanceled(): break
			lines = await_str % divmod(expires_at - time.monotonic(), 60), *detail
			progress = 100 - int(100 * i / expires_in)
			progress_dialog.update('[CR]'.join(lines), progress)
			sleep(1000)
		timer.cancel()
		progress_dialog.close()
		if progress_dialog.iscanceled(): return False
		if not self.token: return notification(32574)
		account_id, access_token = str(data['account_id']), str(data['access_token'])
		set_setting('tmdb.account_id', account_id)
		set_setting('tmdb.token', access_token)
		notification(set_auth_str % cls_name)
		sleep(500)
		if not self.token and not get_setting('tmdb.token'): return
		access_token = self.token or get_setting('tmdb.token')
		data = {'access_token': access_token}
		response = requests.post(self.base_url('3/authentication/session/convert/4'), json=data, headers=self.headers, timeout=timeout)
		result = response.json()
		if not result['success']: return
		session_id = result['session_id']
		params = {'session_id': session_id}
		response = requests.get(self.base_url('3/account'), params=params, headers=self.headers, timeout=timeout)
		result = response.json()
		if not 'id' in result: return
		username, session_account_id = str(result['username']), str(result['id'])
		set_setting('tmdb.username', username)
		set_setting('tmdb.session_id', session_id)
		set_setting('tmdb.session_account_id', session_account_id)
		return True

