"""Tests for file exercises"""
import json
import os
from pathlib import Path
from tempfile import TemporaryDirectory
from textwrap import dedent
from unittest.mock import patch

from helpers import make_file, run_program, ModuleTestCase


class LineNumbersTests(ModuleTestCase):

    """
    Tests for line_numbers.py.

    Output each line with its line number in front of it
    """

    module_path = "line_numbers.py"
    maxDiff = 300

    def run_with_fake_file(self, lines, expected):
        expected_contents = "\n".join(expected) + "\n"
        with make_file("\n".join(lines) + "\n") as filename:
            output = run_program("line_numbers.py", [filename])
            self.assertEqual(output, expected_contents)

    def test_lines(self):
        self.run_with_fake_file(
            ["Oh what a day", "What a lovely day"],
            ["1 Oh what a day", "2 What a lovely day"],
        )

    def test_more_lines(self):
        self.run_with_fake_file(
            [
                "This file",
                "is two lines long.",
                "No wait, it's three lines long!",
            ],
            [
                "1 This file",
                "2 is two lines long.",
                "3 No wait, it's three lines long!",
            ],
        )


class CountTests(ModuleTestCase):

    """
    Tests for count.py.

    Output the number of lines, words, and characters in the given file.
    """

    module_path = "count.py"

    def run_with_fake_file(self, lines):
        with make_file("\n".join(lines) + "\n") as filename:
            return run_program("count.py", [filename]).split("\n")

    def test_lines(self):
        output = self.run_with_fake_file(["Oh what a day", "What a lovely day"])
        self.assertIn("Lines: 2", output)

    def test_words(self):
        output = self.run_with_fake_file(["Oh what a day", "What a lovely day"])
        self.assertIn("Words: 8", output)

    def test_characters(self):
        output = self.run_with_fake_file(["Oh what a day", "What a lovely day"])
        self.assertIn("Characters: 32", output)

    def test_longest_line(self):
        output = self.run_with_fake_file(["Oh what a day", "What a lovely day"])
        self.assertIn("Longest line: 17", output)

    def test_whole_output(self):
        output = self.run_with_fake_file(["This file", "is two lines long"])
        self.assertEqual(
            output,
            [
                "Lines: 2",
                "Words: 6",
                "Characters: 28",
                "Longest line: 17",
                "",
            ],
        )


class ReverseTests(ModuleTestCase):

    """
    Tests for reverse.py

    Reverse a file character-by-character and output to a new file.
    """

    module_path = "reverse.py"

    def test_short_file(self):
        new_filename = "elif_my.txt"
        lines1 = ["This file", "is two lines long"]
        lines2 = ["gnol senil owt si", "elif sihT"]
        with make_file("\n".join(lines1)) as filename:
            run_program("reverse.py", [filename, new_filename])
            with open(new_filename) as reversed_file:
                reversed_lines = reversed_file.read().splitlines()
            os.remove(new_filename)
        self.assertEqual(reversed_lines, lines2)


class ConcatTests(ModuleTestCase):

    """
    Tests for concat.py

    Glue together any number of files and print them to standard output.
    """

    module_path = "concat.py"

    def test_one_file(self):
        with make_file("This is file 1\n") as filename:
            output = run_program("concat.py", [filename])
        self.assertEqual(output, "This is file 1\n")

    def test_two_files(self):
        with make_file("File 1\n") as filename1:
            with make_file("File 2\n") as filename2:
                output = run_program("concat.py", [filename1, filename2])
        self.assertEqual(output, "File 1\nFile 2\n")

    def test_erroneous_file(self):
        with make_file("File 1\n") as filename1:
            filename2 = "non_existant_file.txt"
            with make_file("File 3\n") as filename3:
                files = [filename1, filename2, filename3]
                output, stderr = run_program("concat.py", files, stderr=True)
        self.assertEqual(output, "File 1\nFile 3\n")
        self.assertEqual(
            stderr, "[Errno 2] No such file or directory: 'non_existant_file.txt'\n"
        )


class PassphraseTests(ModuleTestCase):

    """
    Tests for passphrase.py

    Print 4 random words to use as a passphrase.
    """

    module_path = "passphrase.py"

    def test_words_seem_random(self):
        with make_file(WORDS1) as my_file:
            output1 = run_program('passphrase.py', [my_file])
            output2 = run_program('passphrase.py', [my_file])
            self.assertNotEqual(
                output1,
                output2,
                "Running twice results in different words",
            )
            self.assertGreater(
                len(set(output1.split() + output2.split())),
                3,
                "8 words from 50 choices makes >3 unique words",
            )

    def test_four_words_printed(self):
        with make_file(WORDS2) as my_file:
            self.assertRegex(
                run_program('passphrase.py', [my_file]),
                r"\w+ \w+ \w+ \w+\n",
                "Should print 4 words with spaces between them",
            )

    def test_just_two_words_in_file(self):
        with make_file(WORDS3) as my_file:
            self.assertRegex(
                run_program('passphrase.py', [my_file]),
                r"(ivy|perch)( (ivy|perch)){3}\n",
            )


class TodosTests(ModuleTestCase):

    """
    Tests for todos.py.

    Print out the lines that have TODO in them.
    """

    module_path = "todos.py"
    maxDiff = 300

    def run_with_fake_file(self, lines, expected):
        expected_contents = "\n".join(expected + [""])
        with make_file("\n".join(lines) + "\n") as filename:
            output = run_program("todos.py", [filename])
            self.assertEqual(output, expected_contents)

    def test_no_todos(self):
        self.run_with_fake_file(["Oh what a day", "What a lovely day"], [])

    def test_two_todos(self):
        self.run_with_fake_file(
            [
                "This file TODO",
                "is two lines long.",
                "TODO No wait, it's three lines long!",
            ],
            [
                "001 This file TODO",
                "003 TODO No wait, it's three lines long!",
            ],
        )

    def test_many_lines(self):
        self.run_with_fake_file(
            ["a", "b", "c", "TODO", "d", "e", "f", "g", "h", "i", "TODO"],
            [
                "004 TODO",
                "011 TODO",
            ],
        )


class NewReadmeTests(ModuleTestCase):

    """
    Tests for new_readme.py.

    Create a readme.md file with user-provided project name.
    """

    module_path = "new_readme.py"

    def test_basic_functionality(self):
        with TemporaryDirectory() as temp_dir:
            original_dir = os.getcwd()
            try:
                os.chdir(temp_dir)
                with patch("builtins.input", side_effect=["Skynet Lite"]):
                    run_program("new_readme.py")
                    content = Path("readme.md").read_text()
                    self.assertIn("Skynet Lite", content)
                    self.assertNotIn("Super Awesome Project", content)
            finally:
                os.chdir(original_dir)

    def test_multiword_project_name(self):
        with TemporaryDirectory() as temp_dir:
            original_dir = os.getcwd()
            try:
                os.chdir(temp_dir)
                with patch("builtins.input", side_effect=["Super Awesome Project"]):
                    run_program("new_readme.py")
                    content = Path("readme.md").read_text()
                    self.assertIn("Super Awesome Project", content)
                    self.assertNotIn("Skynet Lite", content)
            finally:
                os.chdir(original_dir)


class SortTests(ModuleTestCase):

    """
    Tests for sort.py

    Sort every line in a given file, overwriting the original.
    """

    module_path = "sort.py"

    def test_names(self):
        names = [
            "John Licea",
            "Freddy Colella",
            "James Stell",
            "Mary Carr",
            "Doris Romito",
            "Janet Allen",
            "Suzanne Blevins",
            "Chris Moczygemba",
            "Shawn McCarty",
            "Jennette Holt",
        ]
        with make_file("\n".join(names) + "\n") as filename:
            output = run_program("sort.py", [filename])
            with open(filename) as sorted_file:
                contents = sorted_file.read().splitlines()
        self.assertEqual(output, "")
        self.assertEqual(contents, sorted(names))


class CountryCapitalsTests(ModuleTestCase):

    """
    Tests for country_capitals.py

    Create CSV file of country capitals.
    """

    module_path = "country_capitals.py"

    def test_file_created(self):
        with make_file("") as filename:
            os.remove(filename)
            run_program("country_capitals.py", [filename])
            with open(filename) as csv_file:
                self.assertNotEqual(csv_file.read(), "")

    def test_file_contents(self):
        with make_file("") as filename:
            run_program("country_capitals.py", [filename])
            with open(filename) as csv_file:
                lines = csv_file.read().splitlines()
        self.assertEqual(lines[0], "country,capital,population")
        self.assertEqual(
            lines[1:4],
            [
                "China,Beijing,1330044000",
                "India,New Delhi,1173108018",
                "United States,Washington,310232863",
            ],
        )
        self.assertEqual(len(lines), 253)


class GZipFetchTests(ModuleTestCase):

    """
    Tests for gzip_fetch.py

    Download gzipped data from given URL and save to given filename
    """

    module_path = "gzip_fetch.py"

    def test_file_created(self):
        url = "https://httpbin.org/gzip"
        with make_file("") as filename:
            os.remove(filename)
            run_program("gzip_fetch.py", [url, filename])
            with open(filename) as csv_file:
                self.assertNotEqual(csv_file.read(), "")

    def test_httpbin(self):
        url = "https://httpbin.org/gzip"
        with make_file("") as filename:
            output = run_program("gzip_fetch.py", [url, filename])
            with open(filename) as json_file:
                contents = json_file.read()
        self.assertIn('"method": "GET"', contents)
        response_data = json.loads(contents)
        self.assertTrue(response_data["gzipped"])
        self.assertEqual(output, "")


WORDS1 = dedent("""
    acorn
    aged
    armor
    axis
    baker
    brink
    cape
    crawl
    crib
    deal
    decay
    dice
    dice
    doing
    drown
    eats
    ebony
    flock
    food
    fool
    frisk
    grit
    gummy
    hash
    heat
    jazz
    jog
    jot
    juror
    keg
    old
    petal
    proof
    puma
    repay
    rust
    said
    shore
    smog
    spend
    stood
    sweep
    taps
    taunt
    trade
    tusk
    widow
    wispy
    wok
    wool
""").lstrip("\n")

WORDS2 = dedent("""
    ebook
    creme
    blimp
    voice
    duo
""").lstrip("\n")

WORDS3 = dedent("""
    ivy
    perch
""").lstrip("\n")


if __name__ == "__main__":
    from helpers import error_message

    error_message()
