"""
Concrete :class:`~.base.TrackerBase` subclass for MTV
"""

import re
import urllib

from ... import __project_name__, errors, utils
from ..base import TrackerBase
from .config import MtvTrackerConfig
from .jobs import MtvTrackerJobs

import logging  # isort:skip
_log = logging.getLogger(__name__)


class MtvTracker(TrackerBase):
    name = 'mtv'
    label = 'MTV'

    setup_howto_template = (
        '{howto.introduction}\n'
        '\n'
        '{howto.current_section}. Login Credentials\n'
        '\n'
        '   {howto.current_section}.1 $ upsies set trackers.mtv.username USERNAME\n'
        '   {howto.current_section}.2 $ upsies set trackers.mtv.password PASSWORD\n'
        '\n'
        '{howto.current_section}. Announce URL (Optional)\n'
        '\n'
        '   The announce URL is fetched from the website on demand, but you can\n'
        '   also configure it explicitly.\n'
        '\n'
        '   {howto.current_section}.1 $ upsies set trackers.mtv.announce_url ANNOUNCE_URL\n'
        '{howto.bump_section}'
        '\n'
        '{howto.screenshots}\n'
        '\n'
        '{howto.autoseed}\n'
        '\n'
        '{howto.upload}\n'
    )

    TrackerConfig = MtvTrackerConfig
    TrackerJobs = MtvTrackerJobs

    @property
    def _base_url(self):
        return self.options['base_url']

    @property
    def _login_url(self):
        return urllib.parse.urljoin(self._base_url, '/login')

    @property
    def _logout_url(self):
        return urllib.parse.urljoin(self._base_url, '/logout')

    @property
    def _upload_url(self):
        return urllib.parse.urljoin(self._base_url, '/upload.php')

    @property
    def _torrents_url(self):
        return urllib.parse.urljoin(self._base_url, '/torrents.php')

    async def login(self):
        if not self.is_logged_in:
            if not self.options.get('username'):
                raise errors.RequestError('Login failed: No username configured')
            elif not self.options.get('password'):
                raise errors.RequestError('Login failed: No password configured')

            _log.debug('%s: Logging in as %r', self.name, self.options['username'])
            data = {
                'token': await self._get_token(),
                'username': self.options['username'],
                'password': self.options['password'],
                'cinfo': '1280|720|24|0',
                'iplocked': '1',
                'submit': 'login',
            }
            response = await utils.http.post(
                url=self._login_url,
                user_agent=True,
                data=data,
            )
            doc = utils.html.parse(response)
            self._confirm_login(doc)

    async def logout(self):
        try:
            if self.is_logged_in:
                _log.debug('%s: Logging out', self.name)
                await utils.http.post(
                    url=self._logout_url,
                    data={'token': await self._get_token()},
                    user_agent=True,
                )
        finally:
            if hasattr(self, '_is_logged_in'):
                delattr(self, '_is_logged_in')

    @property
    def is_logged_in(self):
        return bool(getattr(self, '_is_logged_in', False))

    async def get_announce_url(self):
        if self.options.get('announce_url'):
            return self.options['announce_url']
        else:
            await self.login()
            try:
                response = await utils.http.get(self._upload_url, user_agent=True)
                doc = utils.html.parse(response)
                announce_url_tag = doc.find('input', value=re.compile(r'^https?://.*/announce\b'))
                if announce_url_tag:
                    return announce_url_tag['value']
                else:
                    cmd = f'{__project_name__} set trackers.{self.name}.announce_url <YOUR URL>'
                    raise errors.RequestError(f'Failed to find announce URL - set it manually: {cmd}')
            finally:
                # TODO: If we logout, we can't login again for some reason. The
                #       error message is: Unauthorized: Expired session await
                # self.logout()
                pass

    async def upload(self, tracker_jobs):
        autofill_post_data = await self._make_autofill_request(tracker_jobs)
        _log.debug('Autofill data: %r', autofill_post_data)

        torrent_page_url = await self._make_upload_request(tracker_jobs, autofill_post_data)
        _log.debug('Torrent page URL: %r', torrent_page_url)
        return torrent_page_url

    async def _make_autofill_request(self, tracker_jobs):
        # First request uploads the torrent file. We extract "tempfileid" and
        # "tempfilename" from the returned HTML form that must be returned in
        # the second request. We can also extract the autogenerated "taglist".
        post_data = await self._prepare_post_data(tracker_jobs.post_data_autofill)
        post_files = {
            'file_input': {
                'file': tracker_jobs.torrent_filepath,
                'mimetype': 'application/x-bittorrent',
            },
        }

        response = await utils.http.post(
            url=self._upload_url,
            cache=False,
            user_agent=True,
            follow_redirects=False,
            data=post_data,
            files=post_files,
        )

        doc = utils.html.parse(response)
        try:
            return {
                'tempfileid': self._get_form_value(doc, 'input', attrs={'name': 'tempfileid'}),
                'tempfilename': self._get_form_value(doc, 'input', attrs={'name': 'tempfilename'}),
                'taglist': self._get_form_value(doc, 'textarea', attrs={'name': 'taglist'}),
            }
        except ValueError as e:
            # If we can't find required values, look for error message
            _log.debug('Failed to extract values from autofill response: %s', e)
            self._raise_error(doc, msg_prefix='Upload failed')

    async def _make_upload_request(self, tracker_jobs, autofill_post_data):
        # Second request combines our metadata with server-generated data from
        # _make_autofill_request().
        post_data = await self._prepare_post_data(tracker_jobs.post_data_upload)
        post_data.update(autofill_post_data)

        response = await utils.http.post(
            url=self._upload_url,
            cache=False,
            user_agent=True,
            follow_redirects=False,
            data=post_data,
        )

        # Get URL to uploaded torrent from HTTP 302 redirect location
        redirect_path = response.headers.get('location', '')
        _log.debug('HTTP 302 redirect location: %r', redirect_path)
        if 'torrents.php' in redirect_path:
            torrent_page_url = urllib.parse.urljoin(self._base_url, redirect_path)
            return torrent_page_url

        # Find error message in HTML
        doc = utils.html.parse(response)
        self._raise_error_dupes(doc)
        try:
            self._raise_error(doc, msg_prefix='Upload failed')
        except RuntimeError:
            # _raise_error() can't find an error message.
            # Dump response headers for debugging.
            # HTML was already dumped by _raise_error()
            headers_filepath = f'{utils.fs.basename(tracker_jobs.content_path)}.headers'
            with open(headers_filepath, 'w') as f:
                for k, v in response.headers.items():
                    f.write(f'{k}: {v}\n')
            raise

    async def _prepare_post_data(self, post_data):
        # Remove `None` values from post_data values and ensure all keys and
        # values are strings.
        post_data = {
            str(k): str(v)
            for k, v in post_data.items()
            if v is not None
        }
        post_data['auth'] = await self._get_auth()
        return post_data

    async def _get_token(self):
        # "token" seems to be a random string that changes regularly (maybe
        # every minute?), and it must be returned in the login/logout requests
        url = self._base_url if self.is_logged_in else self._login_url
        response = await utils.http.get(url, user_agent=True)
        doc = utils.html.parse(response)
        return self._get_form_value(doc, 'input', attrs={'name': 'token'})

    async def _get_auth(self):
        # "auth" seems to be a random string that never changes, and it must be
        # returned in upload requests
        if not hasattr(self, '_auth'):
            await self.login()
            response = await utils.http.get(self._upload_url, user_agent=True)
            doc = utils.html.parse(response)
            self._auth = self._get_form_value(doc, 'input', attrs={'name': 'auth'})
        return self._auth

    def _get_form_value(self, doc, tag_name, **kwargs):
        try:
            tag = doc.find(tag_name, **kwargs)

            # <input value="...">
            value = tag.get('value')
            if value:
                return value

            # <textarea>value</textarea>
            value = utils.html.as_text(tag)
            if value:
                return value

            raise ValueError(f'Tag has no value: {tag}')

        except AttributeError:
            pass

        raise ValueError(f'Could not find tag: {tag_name}')

    def _confirm_login(self, doc):
        user_page_link_regex = re.compile(r'/user.php(?:\?|.*?&)id=(\d+)')
        user_page_link_tag = doc.find('a', href=user_page_link_regex)
        if user_page_link_tag:
            match = user_page_link_regex.search(user_page_link_tag['href'])
            self._user_id = match.group(1)
            self._is_logged_in = True
        else:
            self._user_id = None
            self._is_logged_in = False
            self._raise_error(doc, msg_prefix='Login failed')

    def _raise_error(self, doc, msg_prefix):
        error_tag = doc.find('div', attrs={'class': 'error'})
        if error_tag:
            msg = utils.html.as_text(error_tag)
            raise errors.RequestError(f'{msg_prefix}: {msg}')

        # Example error: "The exact same torrent file already exists on the site!"
        alert_tag = doc.find('div', attrs={'class': 'alert'})
        if alert_tag:
            msg = utils.html.as_text(alert_tag)
            raise errors.RequestError(f'{msg_prefix}: {msg}')

        filepath = f'{msg_prefix}.html'
        utils.html.dump(doc, filepath)
        raise RuntimeError(f'{msg_prefix}: No error message found (dumped HTML to {filepath})')

    def _raise_error_dupes(self, doc):
        dupes_warning = doc.find('div', id="messagebar", string=re.compile(r'(?i:dupes?|duplicates?)'))
        if dupes_warning:
            _log.debug('Found dupes warning: %s', dupes_warning)
            dupe_files = []
            table = doc.find('table', id='torrent_table')
            if table:
                for row in table.find_all('tr', attrs={'class': 'torrent'}):
                    for cell in row.find('td', attrs={'class': 'torrent'}):
                        dupe_files.append(utils.html.as_text(cell))

            if dupe_files:
                raise errors.FoundDupeError(dupe_files)

    # We don't seem to need _get_torrent_page_url() because we can get the
    # torrent page from the HTTP 302 redirect response in
    # _make_upload_request().
    #
    # However, this redirects to the torrent group, not to the individual
    # torrent we uploaded. Assume nobody cares, but keep the code around since
    # it might come in handy in the future. If that's the case, add tests!

    # async def _get_torrent_page_url(self, release_title):
    #     # Find link that contains `release_title` in list of uploaded torrents

    #     _log.debug('Getting torrent page URL for %r', release_title)
    #     await self.login()

    #     assert self._user_id, self._user_id
    #     torrents_url = f'{self._torrents_url}?type=uploaded&userid={self._user_id}'
    #     _log.debug('Getting torrents from %r', torrents_url)

    #     response = await utils.http.get(torrents_url, user_agent=True)
    #     # _log.debug('Torrents response: %r', response)
    #     doc = utils.html.parse(response)
    #     # _log.debug('Torrents page: %s', doc.prettify())

    #     try:
    #         table = doc.find('table', attrs={'class': 'torrent_table'})
    #         # _log.debug('Torrents table: %s', table.prettify())

    #         release_title_regex = re.compile(rf'{re.escape(str(release_title))}')

    #         for row in table.find_all('tr', attrs={'class': 'torrent'}):
    #             # _log.debug('Torrents row: %s', row.prettify())
    #             torrent_page_link = row.find('a', string=release_title_regex)
    #             if torrent_page_link:
    #                 _log.debug('Torrent page URL: %s: %r', release_title_regex, torrent_page_link)
    #                 return urllib.parse.urljoin(self._base_url, torrent_page_link['href'])

    #     except (AttributeError, KeyError):
    #         pass

    #     raise ValueError(f'Could not find torrent page URL: {release_title}')
