# SPDX-FileCopyrightText: Antoni Boucher (antoyo) <bouanto@zoho.com>
# SPDX-FileCopyrightText: Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later

import os
import dataclasses

import pytest
import bs4
from qutebrowser.qt.core import QUrl
from qutebrowser.qt.network import QNetworkRequest

from qutebrowser.browser.webkit.network import filescheme
from qutebrowser.utils import urlutils, utils
from helpers import testutils


@pytest.mark.parametrize('create_file, create_dir, filterfunc, expected', [
    (True, False, os.path.isfile, True),
    (True, False, os.path.isdir, False),

    (False, True, os.path.isfile, False),
    (False, True, os.path.isdir, True),

    (False, False, os.path.isfile, False),
    (False, False, os.path.isdir, False),
])
def test_get_file_list(tmpdir, create_file, create_dir, filterfunc, expected):
    """Test get_file_list."""
    path = tmpdir / 'foo'
    if create_file or create_dir:
        path.ensure(dir=create_dir)

    all_files = os.listdir(tmpdir)

    result = filescheme.get_file_list(str(tmpdir), all_files, filterfunc)
    item = {'name': 'foo', 'absname': str(path)}
    assert (item in result) == expected


class TestIsRoot:

    @pytest.mark.windows
    @pytest.mark.parametrize('directory, is_root', [
        ('C:\\foo\\bar', False),
        ('C:\\foo\\', False),
        ('C:\\foo', False),
        ('C:\\', True)
    ])
    def test_windows(self, directory, is_root):
        assert filescheme.is_root(directory) == is_root

    @pytest.mark.posix
    @pytest.mark.parametrize('directory, is_root', [
        ('/foo/bar', False),
        ('/foo/', False),
        ('/foo', False),
        ('/', True)
    ])
    def test_posix(self, directory, is_root):
        assert filescheme.is_root(directory) == is_root


class TestParentDir:

    @pytest.mark.windows
    @pytest.mark.parametrize('directory, parent', [
        ('C:\\foo\\bar', 'C:\\foo'),
        ('C:\\foo', 'C:\\'),
        ('C:\\foo\\', 'C:\\'),
        ('C:\\', 'C:\\'),
    ])
    def test_windows(self, directory, parent):
        assert filescheme.parent_dir(directory) == parent

    @pytest.mark.posix
    @pytest.mark.parametrize('directory, parent', [
        ('/home/foo', '/home'),
        ('/home', '/'),
        ('/home/', '/'),
        ('/', '/'),
    ])
    def test_posix(self, directory, parent):
        assert filescheme.parent_dir(directory) == parent


def _file_url(path):
    """Return a file:// url (as string) for the given LocalPath.

    Arguments:
        path: The filepath as LocalPath (as handled by py.path)
    """
    return urlutils.file_url(str(path))


class TestDirbrowserHtml:

    @dataclasses.dataclass
    class Parsed:

        parent: str
        folders: list[str]
        files: list[str]

    @dataclasses.dataclass
    class Item:

        link: str
        text: str

    @pytest.fixture
    def parser(self):
        """Provide a function to get a parsed dirbrowser document."""
        def parse(path):
            html = filescheme.dirbrowser_html(path).decode('utf-8')
            soup = bs4.BeautifulSoup(html, 'html.parser')

            with testutils.ignore_bs4_warning():
                print(soup.prettify())

            container = soup('div', id='dirbrowserContainer')[0]

            parent_elem = container('ul', class_='parent')
            if not parent_elem:
                parent = None
            else:
                parent = parent_elem[0].li.a.string

            folders = []
            files = []

            for li in container('ul', class_='folders')[0]('li'):
                item = self.Item(link=li.a['href'], text=str(li.a.string))
                folders.append(item)

            for li in container('ul', class_='files')[0]('li'):
                item = self.Item(link=li.a['href'], text=str(li.a.string))
                files.append(item)

            return self.Parsed(parent=parent, folders=folders, files=files)

        return parse

    def test_basic(self):
        html = filescheme.dirbrowser_html(os.getcwd()).decode('utf-8')
        soup = bs4.BeautifulSoup(html, 'html.parser')

        with testutils.ignore_bs4_warning():
            print(soup.prettify())

        container = soup.div
        assert container['id'] == 'dirbrowserContainer'
        title_elem = container('div', id='dirbrowserTitle')[0]
        title_text = title_elem('p', id='dirbrowserTitleText')[0].text
        assert title_text == 'Browse directory: {}'.format(os.getcwd())

    def test_icons(self, monkeypatch):
        """Make sure icon paths are correct file:// URLs."""
        html = filescheme.dirbrowser_html(os.getcwd()).decode('utf-8')
        soup = bs4.BeautifulSoup(html, 'html.parser')

        with testutils.ignore_bs4_warning():
            print(soup.prettify())

        css = soup.html.head.style.string
        assert "background-image: url('qute://resource/img/folder.svg');" in css

    def test_empty(self, tmpdir, parser):
        parsed = parser(str(tmpdir))
        assert parsed.parent
        assert not parsed.folders
        assert not parsed.files

    def test_files(self, tmpdir, parser):
        foo_file = tmpdir / 'foo'
        bar_file = tmpdir / 'bar'
        foo_file.ensure()
        bar_file.ensure()

        parsed = parser(str(tmpdir))
        assert parsed.parent
        assert not parsed.folders
        foo_item = self.Item(_file_url(foo_file), foo_file.relto(tmpdir))
        bar_item = self.Item(_file_url(bar_file), bar_file.relto(tmpdir))
        assert parsed.files == [bar_item, foo_item]

    def test_html_special_chars(self, tmpdir, parser):
        special_file = tmpdir / 'foo&bar'
        special_file.ensure()

        parsed = parser(str(tmpdir))
        item = self.Item(_file_url(special_file), special_file.relto(tmpdir))
        assert parsed.files == [item]

    def test_dirs(self, tmpdir, parser):
        foo_dir = tmpdir / 'foo'
        bar_dir = tmpdir / 'bar'
        foo_dir.ensure(dir=True)
        bar_dir.ensure(dir=True)

        parsed = parser(str(tmpdir))
        assert parsed.parent
        assert not parsed.files
        foo_item = self.Item(_file_url(foo_dir), foo_dir.relto(tmpdir))
        bar_item = self.Item(_file_url(bar_dir), bar_dir.relto(tmpdir))
        assert parsed.folders == [bar_item, foo_item]

    def test_mixed(self, tmpdir, parser):
        foo_file = tmpdir / 'foo'
        bar_dir = tmpdir / 'bar'
        foo_file.ensure()
        bar_dir.ensure(dir=True)

        parsed = parser(str(tmpdir))
        foo_item = self.Item(_file_url(foo_file), foo_file.relto(tmpdir))
        bar_item = self.Item(_file_url(bar_dir), bar_dir.relto(tmpdir))
        assert parsed.parent
        assert parsed.files == [foo_item]
        assert parsed.folders == [bar_item]

    def test_root_dir(self, tmpdir, parser):
        root_dir = 'C:\\' if utils.is_windows else '/'
        parsed = parser(root_dir)
        assert not parsed.parent

    def test_oserror(self, mocker):
        m = mocker.patch('qutebrowser.browser.webkit.network.filescheme.'
                         'os.listdir')
        m.side_effect = OSError('Error message')
        html = filescheme.dirbrowser_html('').decode('utf-8')
        soup = bs4.BeautifulSoup(html, 'html.parser')

        with testutils.ignore_bs4_warning():
            print(soup.prettify())

        error_msg = soup('p', id='error-message-text')[0].string
        assert error_msg == 'Error message'


class TestFileSchemeHandler:

    def test_dir(self, tmpdir):
        url = QUrl.fromLocalFile(str(tmpdir))
        req = QNetworkRequest(url)
        reply = filescheme.handler(req, None, None)
        # The URL will always use /, even on Windows - so we force this here
        # too.
        tmpdir_path = str(tmpdir).replace(os.sep, '/')
        assert reply.readAll() == filescheme.dirbrowser_html(tmpdir_path)

    def test_file(self, tmpdir):
        filename = tmpdir / 'foo'
        filename.ensure()
        url = QUrl.fromLocalFile(str(filename))
        req = QNetworkRequest(url)
        reply = filescheme.handler(req, None, None)
        assert reply is None

    def test_unicode_encode_error(self, mocker):
        url = QUrl('file:///tmp/foo')
        req = QNetworkRequest(url)

        err = UnicodeEncodeError('ascii', '', 0, 2, 'foo')
        mocker.patch('os.path.isdir', side_effect=err)

        reply = filescheme.handler(req, None, None)
        assert reply is None
