# This program 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 2 of the License, or
# (at your option) any later version.

import io
from bz2 import BZ2Compressor
from http.server import HTTPServer, BaseHTTPRequestHandler
from threading import Thread
from typing import Generator

import pytest

import quodlibet.config
from quodlibet.browsers.iradio import (InternetRadio, IRFile, QuestionBar,
                                       parse_taglist, parse_pls, parse_m3u,
                                       download_taglist)
from quodlibet.formats import AudioFile
from quodlibet.library import SongLibrary
from quodlibet.util import is_windows, is_osx
from tests import TestCase, skipIf, run_gtk_loop

quodlibet.config.RATINGS = quodlibet.config.HardCodedRatingsPrefs()

FAKE_URLS = ["http://example.com", "https://quodlibet.readthedocs.io"]


def test_parse_taglist():
    parse_taglist(b"")
    stations = parse_taglist(b"""\
uri=http://foo.bar
artist=foo
artist=bar
~listenerpeak=42
""")

    assert len(stations) == 1
    assert stations[0]["~#listenerpeak"] == 42
    assert stations[0].list("artist") == ["foo", "bar"]


class FakeTask:
    def __init__(self):
        self.pulsed = 0

    def pulse(self):
        self.pulsed += 1


def test_parse_pls():
    f = io.BytesIO(b"""\
[playlist]
Title1=Here enter name of the station
File1=http://stream2.streamq.net:8020/
NumberOfEntries=1
""")

    r = parse_pls(f)
    assert len(r) == 1
    assert r[0]("~uri") == "http://stream2.streamq.net:8020/"
    assert r[0]("title") == "Here enter name of the station"


def test_parse_m3u():
    f = io.BytesIO(b"""\
#EXTM3U

#EXTINF:123, Sample artist - Sample title
http://stream2.streamq.net:8020/
""")

    r = parse_m3u(f)
    assert len(r) == 1
    assert r[0]("~uri") == "http://stream2.streamq.net:8020/"


class TQuestionBar(TestCase):

    def test_main(self):
        b = QuestionBar()
        self.assertFalse(b.get_visible())


class TInternetRadio(TestCase):
    def setUp(self):
        quodlibet.config.init()
        self.bar = InternetRadio(SongLibrary())

    def test_can_filter(self):
        self.assertTrue(self.bar.can_filter("foo"))
        self.assertTrue(self.bar.can_filter_text())

    def test_status_bar_text(self):
        self.assertEqual(self.bar.status_text(1), "1 station")
        self.assertEqual(self.bar.status_text(101, 123), "101 stations")

    @skipIf(is_windows() or is_osx(), "Don't need to test downloads all the time")
    def test_click_add_station(self):
        self.bar._update_button.emit('clicked')
        assert not self.bar.has_stations
        # Run the actual download from real URL
        run_gtk_loop()
        assert self.bar.has_stations

    def tearDown(self):
        self.bar.destroy()
        quodlibet.config.quit()


class TIRFile(TestCase):
    def setUp(self):
        self.s = IRFile("http://foo.bar")

    def test_website(self):
        self.s["website"] = "abc"
        self.assertEqual(self.s.get("artist"), "abc")
        self.assertEqual(self.s("artist"), "abc")
        self.assertEqual(self.s.list("artist"), ["abc"])

    def test_organisation(self):
        self.s["organization"] = "foo"
        self.assertEqual(self.s("title"), "foo")
        self.assertEqual(self.s.get("title"), "foo")

    def test_title_split_stream(self):
        self.assertFalse(self.s("artist"))
        self.s["title"] = "artist - title"
        self.s.multisong = False
        self.assertEqual(self.s("title"), "title")
        self.assertEqual(self.s.get("title"), "title")
        self.assertEqual(self.s("artist"), "artist")
        self.assertEqual(self.s.get("artist"), "artist")

    def test_title_split(self):
        self.assertTrue(self.s.multisong)
        self.s["title"] = "artist - title"
        self.assertEqual(self.s("title"), self.s["title"])

    def test_format(self):
        self.assertEqual(self.s("~format"), self.s.format)
        self.s["audio-codec"] = "SomeCodec"
        self.assertTrue("SomeCodec" in self.s("~format"))
        self.assertTrue(self.s.format in self.s("~format"))

    def test_people(self):
        self.s["title"] = "artist - title"
        self.s.multisong = False
        self.assertEqual(self.s("~people"), "artist")
        self.assertEqual(self.s("~~people~foo"), "artist")

    def testcan_write(self):
        self.failUnless(self.s.can_change("title"))
        self.s.streamsong = True
        self.failIf(self.s.can_change("title"))

    def test_dump_to_file(self):
        self.s["title"] = "artist - title"
        self.s.multisong = False
        dump = self.s.to_dump()
        new = AudioFile()
        new.from_dump(dump)
        self.assertEqual(new["title"], "title")
        self.assertEqual(new["artist"], "artist")

        del self.s["title"]
        dump = self.s.to_dump()
        new = AudioFile()
        new.from_dump(dump)
        self.assertTrue("title" not in new)
        self.assertTrue("artist" not in new)


class Bzip2GetHandler(BaseHTTPRequestHandler):

    # Not thread-safe, but won't be an issue here
    compressor = BZ2Compressor()

    def do_GET(self) -> None:
        uris = [f"uri={url}" for url in FAKE_URLS]
        content = "\n".join(uris).encode("utf-8")
        self.compressor.compress(content)
        bz2 = self.compressor.flush()
        self.send_response(200)
        self.send_header("Content-Length", str(len(bz2)))
        self.send_header("Content-type", "application/x-bzip2")
        self.end_headers()
        self.wfile.write(bz2)


@pytest.fixture
def test_server() -> Generator[HTTPServer, None, None]:
    server = HTTPServer(("localhost", 0), Bzip2GetHandler)
    Thread(target=server.serve_forever, daemon=True).start()
    yield server
    server.shutdown()


def test_download_tags(test_server):
    received = []
    host, port = test_server.server_address
    url = f"http://{host}:{port:d}"

    ret = list(download_taglist(url, received.extend, None))
    run_gtk_loop()
    assert len(ret), "No stations"
    assert all(ret), "Got some falsey stations"
    assert received, f"No stations received from {url}"
    assert {s("~filename") for s in received} == set(FAKE_URLS)
