from pathlib import Path

import pytest
from flask import url_for

from registry.donor.models import Batch, DonorsOverview, Record
from registry.extensions import db

from .helpers import login


class TestImport:
    """Test of imports"""

    @pytest.mark.parametrize("donation_center_id", range(1, 5))
    @pytest.mark.parametrize(
        "input_file",
        ("tests/data/valid_import.txt", "tests/data/valid_import_single_record.txt"),
    )
    def test_valid_input(self, user, testapp, input_file, donation_center_id):
        input_data = Path(input_file).read_text(encoding="utf-8")
        new_records = len(input_data.strip().splitlines())
        existing_records = Record.query.count()
        existing_batches = Batch.query.filter(
            Batch.donation_center_id == donation_center_id
        ).count()

        login(user, testapp)
        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["input_data"] = input_data
        form.fields["donation_center_id"][0].select(donation_center_id)
        res = form.submit().follow()
        assert "Import proběhl úspěšně" in res
        assert res.status_code == 200

        if input_file.endswith("single_record.txt"):
            expected_page = url_for("donor.detail", rc=input_data.split(";")[0])
        else:
            expected_page = url_for("donor.overview")

        assert res.request.path == expected_page

        assert Record.query.count() == existing_records + new_records
        assert (
            Batch.query.filter(Batch.donation_center_id == donation_center_id).count()
            == existing_batches + 1
        )

    def test_repairable_input(self, user, testapp):
        """Tests an input file the import machinery should be able
        repair automatically without any manual assistance from user"""
        input_data = Path("tests/data/repairable_import.txt").read_text(
            encoding="utf-8"
        )
        new_records = 12  # 15 lines in file - 2 empty lines - 1 without free donations
        existing_records = Record.query.count()
        existing_batches = Batch.query.count()

        login(user, testapp)
        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["donation_center_id"] = 1
        form["input_data"] = input_data
        res = form.submit()
        assert res.status_code == 200
        form = res.forms["importForm"]
        # There is one valid line
        assert len(form["valid_lines"].value.splitlines()) == 1
        # And the rest are invalid lines
        assert len(form["invalid_lines"].value.splitlines()) == new_records - 1
        # We have to have as many lines of errors as invalid lines
        assert len(form["invalid_lines_errors"].value.splitlines()) == len(
            form["invalid_lines"].value.splitlines()
        )
        # But everything should be fixed by the app so we should be
        # ready to just submit the form again and see a sucessful import
        res = form.submit().follow()
        assert "Import proběhl úspěšně" in res
        assert res.status_code == 200
        assert Record.query.count() == existing_records + new_records
        assert Batch.query.count() == existing_batches + 1

    def test_invalid_input(self, user, testapp):
        """Tests an invalid import the app cannot fix automaticaly"""
        input_data = Path("tests/data/invalid_import.txt").read_text(encoding="utf-8")
        existing_records = Record.query.count()
        existing_batches = Batch.query.count()
        login(user, testapp)
        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["donation_center_id"] = 1
        form["input_data"] = input_data
        # No matter how many times we submit the form because it contains
        # invalid records so they never be imported
        for _ in range(5):
            res = form.submit()
            assert res.status_code == 200
            form = res.forms["importForm"]
            assert len(form["valid_lines"].value.splitlines()) == 0
            # And the rest are invalid lines
            assert len(form["invalid_lines"].value.splitlines()) == 9
            # We have to have as many lines of errors as invalid lines
            assert len(form["invalid_lines_errors"].value.splitlines()) == len(
                form["invalid_lines"].value.splitlines()
            )
            assert "Import proběhl úspěšně" not in res
        assert Record.query.count() == existing_records
        assert Batch.query.count() == existing_batches

    def test_invalid_input_invalid_rcs(self, user, testapp):
        input_data = Path("tests/data/invalid_rc.txt").read_text(encoding="utf-8")
        existing_records = Record.query.count()
        existing_batches = Batch.query.count()
        login(user, testapp)
        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["donation_center_id"] = 1
        form["input_data"] = input_data
        res = form.submit()
        assert res.status_code == 200
        form = res.forms["importForm"]
        assert len(form["valid_lines"].value.splitlines()) == 0
        assert len(form["invalid_lines"].value.splitlines()) == 5
        assert len(form["invalid_lines_errors"].value.splitlines()) == len(
            form["invalid_lines"].value.splitlines()
        )
        assert "příliš dlouhé" in form["invalid_lines_errors"].value
        assert "příliš krátké" in form["invalid_lines_errors"].value
        assert "chybí" in form["invalid_lines_errors"].value
        assert "není číselné" in form["invalid_lines_errors"].value
        assert "Import proběhl úspěšně" not in res
        assert Record.query.count() == existing_records
        assert Batch.query.count() == existing_batches

    def test_valid_manual_input(self, user, testapp):
        input_data = Path("tests/data/valid_import.txt").read_text(encoding="utf-8")
        new_records = len(input_data.strip().splitlines())
        existing_records = Record.query.count()
        existing_batches = Batch.query.filter(
            Batch.donation_center_id.is_(None)
        ).count()

        login(user, testapp)
        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["input_data"] = input_data
        form.fields["donation_center_id"][0].select(-1)
        assert (
            form.fields["donation_center_id"][0].selectedIndex
            == len(form.fields["donation_center_id"][0].options) - 1
        )
        res = form.submit().follow()
        assert "Import proběhl úspěšně" in res
        assert res.status_code == 200

        assert Record.query.count() == existing_records + new_records
        assert (
            Batch.query.filter(Batch.donation_center_id.is_(None)).count()
            == existing_batches + 1
        )

    def test_invalid_donation_center(self, user, testapp):
        existing_records = Record.query.count()
        existing_batches = Batch.query.count()

        login(user, testapp)
        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form.fields["donation_center_id"][0].options.append(("666", False, "malicious"))
        form.fields["donation_center_id"][0].select(666)
        res = form.submit()
        assert "Odběrné místo - Not a valid choice" in res

        assert Record.query.count() == existing_records
        assert Batch.query.count() == existing_batches

    @pytest.mark.parametrize(
        "input_file",
        (
            "tests/data/valid_import_multiple_rc.txt",
            "tests/data/valid_import_multiple_rc_dups.txt",
        ),
    )
    def test_valid_import_with_multiple_rc(self, input_file, user, testapp):
        input_data = Path(input_file).read_text(encoding="utf-8")
        new_records = len(input_data.strip().splitlines())
        existing_records = Record.query.count()
        existing_batches = Batch.query.count()

        login(user, testapp)
        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["donation_center_id"] = 1
        form["input_data"] = input_data
        res = form.submit().follow()
        assert "Import proběhl úspěšně" in res
        assert res.status_code == 200

        assert Record.query.count() == existing_records + new_records
        assert Batch.query.count() == existing_batches + 1

        db.session.get(DonorsOverview, "205225299").donation_count_total == 70
        db.session.get(DonorsOverview, "1860231599").donation_count_total == 6

    def test_zero_donations(self, user, testapp):
        # three lines in the input file end with zero and should
        # be automatically ommited from the import
        ends_with_zero = 3
        input_data = Path("tests/data/valid_import_zeroes.txt").read_text(
            encoding="utf-8"
        )
        new_records = len(input_data.strip().splitlines()) - ends_with_zero
        existing_records = Record.query.count()
        existing_batches = Batch.query.count()

        login(user, testapp)
        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["donation_center_id"] = 1
        form["input_data"] = input_data
        res = form.submit().follow()
        assert "Import proběhl úspěšně" in res
        assert res.status_code == 200

        assert Record.query.count() == existing_records + new_records
        assert Batch.query.count() == existing_batches + 1

    def test_empty_input(self, user, testapp):
        """Regression test for issue #118"""
        existing_batches = Batch.query.count()

        # Test regular empty input
        login(user, testapp)
        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["donation_center_id"] = 1
        form["input_data"] = ""
        res = form.submit()

        assert res.status_code == 200
        assert "Vstupní data z odběrného místa - Chybí vstupní data" in res
        assert Batch.query.count() == existing_batches

        # Test empty input for data repair form
        existing_batches = Batch.query.count()
        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["donation_center_id"] = 1
        form["input_data"] = "invalid"
        res = form.submit()

        form = res.forms["importForm"]
        form["invalid_lines"] = ""
        res = form.submit()

        assert res.status_code == 200
        assert "Vstupní data z odběrného místa - Chybí vstupní data" in res
        assert Batch.query.count() == existing_batches

    def test_all_zero_donations(self, user, testapp):
        """
        Tests if the system displays a warning for an input with all of the
        donations set to 0
        """
        login(user, testapp)
        expected_msg = (
            "Vstupní data z odběrného místa - Ze vstupních dat není po filtraci"
            + " co importovat"
        )

        # Test a correct but empty input
        existing_batches = Batch.query.count()

        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["donation_center_id"] = 1
        form["input_data"] = "1;a;b;c;d;00000;000;\n2;a;b;c;d;00000;000;0"
        res = form.submit()

        assert res.status_code == 200
        assert expected_msg in res
        assert Batch.query.count() == existing_batches
        # We don't want the data repair form to show up
        assert "Řádky s chybami" not in res

        # Test an input with an invalid line which is then set to 0
        existing_batches = Batch.query.count()

        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["donation_center_id"] = 1
        form["input_data"] = "1;a;b;c;d;00000;000;\ninvalid"
        res = form.submit()

        form = res.forms["importForm"]
        form["invalid_lines"] = "1;a;b;c;d;00000;000;"
        res = form.submit()

        assert res.status_code == 200
        assert expected_msg in res
        assert Batch.query.count() == existing_batches
        # We don't want the data repair form to show up
        assert "Řádky s chybami" not in res

    @pytest.mark.parametrize(
        ("input_line", "expected_first_name", "expected_last_name"),
        (
            (
                "205225295;TOMÁŠ;VESELÁ,dr;HAMERSKÁ 810;VELKÉ HAMRY;31777;985;69",
                "Dr. TOMÁŠ",
                "VESELÁ",
            ),
            (
                "0352152680;LUBOMÍR;ŠIMEK,BCA.;HUSOVA 2;IVANOVICE NA HANÉ;69710;409;1",
                "BcA. LUBOMÍR",
                "ŠIMEK",
            ),
            (
                "1860231538;VÁCLAV;MAREŠOVÁ    rndr;RYCHVALDSKÁ 11;RÝMAŘOV;11252;180;6",
                "RNDr. VÁCLAV",
                "MAREŠOVÁ",
            ),
            (
                "431229128;MARTIN;DVOŘÁK ing.MGR.;SKALICE 451;MILETÍN;99473;515;3",
                "Ing. Mgr. MARTIN",
                "DVOŘÁK",
            ),
            (
                "0112072730;DANIEL;DOLEŽAL,bca,phdr;ROŽNOVSKÁ 1;PŘEŠTICE;54847;157;14",
                "BcA. PhDr. DANIEL",
                "DOLEŽAL",
            ),
            (
                "6458313785;JAN;MARKOVÁ mudr;DOLNÍ BEČVA 340;PŘIMDA;08321;571;14",
                "MUDr. JAN",
                "MARKOVÁ",
            ),
            (
                "6558232494;JAN;POLÁKOVÁ,MVDR.;REKREAČNÍ 685;LIBEREC;83535;708;16",
                "MVDr. JAN",
                "POLÁKOVÁ",
            ),
            (
                "160718283;VOJTĚCH;URBAN MGA.;KOZLOVICE 247;HARTMANICE;13986;192;121",
                "MgA. VOJTĚCH",
                "URBAN",
            ),
            (
                "0255231647;MILAN;ČERMÁK ing.arch.;MIKULŮVKA 290;KOJETÍN;61964;922;45",
                "Ing. arch. MILAN",
                "ČERMÁK",
            ),
        ),
    )
    def test_import_degrees(
        self, input_line, expected_first_name, expected_last_name, user, testapp
    ):
        login(user, testapp)
        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["input_data"] = input_line
        form.fields["donation_center_id"][0].select(1)
        res = form.submit().follow()
        assert "Import proběhl úspěšně" in res
        assert res.status_code == 200

        rodne_cislo = input_line.split(";")[0]
        donor = db.session.get(DonorsOverview, rodne_cislo)

        assert donor.first_name == expected_first_name
        assert donor.last_name == expected_last_name

    @pytest.mark.parametrize(
        ("expr", "result"),
        (
            ("1+9", 10),
            ("9+1", 10),
            ("10+10", 20),
            ("1+0", 1),
            ("0+1", 1),
            ("50+50", 100),
        ),
    )
    def test_import_with_sum_valid(self, user, testapp, expr, result):
        login(user, testapp)

        existing_batches = Batch.query.count()

        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["donation_center_id"] = 1
        form["input_data"] = f"111111111;a;b;c;d;00000;000;{expr}"
        res = form.submit()

        assert res.status_code == 200
        assert Batch.query.count() == existing_batches
        assert "Řádky s chybami" in res

        form = res.forms["importForm"]
        assert form["invalid_lines"].value == f"111111111;a;b;c;d;00000;000;{result}\n"
        assert form["invalid_lines_errors"].value == f"vstup {expr} sečten = {result}\n"
        res = form.submit().follow()

        assert res.status_code == 200
        assert Batch.query.count() == existing_batches + 1

    @pytest.mark.parametrize(
        ("expr"),
        (
            "+9",
            "9+",
            "10-10",
            "1-0",
            "-1",
            "50+_POČET_",
        ),
    )
    def test_import_with_sum_invalid(self, user, testapp, expr):
        login(user, testapp)

        existing_batches = Batch.query.count()

        res = testapp.get(url_for("batch.import_data"))
        form = res.forms["importForm"]
        form["donation_center_id"] = 1
        form["input_data"] = f"111111111;a;b;c;d;00000;000;{expr}"
        res = form.submit()

        assert res.status_code == 200
        assert Batch.query.count() == existing_batches
        assert "Řádky s chybami" in res

        for _ in range(2):
            form = res.forms["importForm"]
            assert (
                form["invalid_lines"].value == f"111111111;a;b;c;d;00000;000;{expr}\n"
            )
            assert form["invalid_lines_errors"].value == "nevalidní počet odběrů\n"
            res = form.submit()

            assert res.status_code == 200
            assert Batch.query.count() == existing_batches

    @pytest.mark.parametrize(
        "filename,valid_lines,skipped_lines",
        (
            ("trinec_import_valid_tb.xlsx", 6, 1),
            ("trinec_import_valid_pb.xlsx", 4, 1),
            ("trinec_import_skip_lines.xlsx", 1, 6),
        ),
    )
    def test_prepare_trinec_valid_file(
        self, user, testapp, filename, valid_lines, skipped_lines
    ):
        """Test uploading valid Třinec Excel file"""
        login(user, testapp)

        # Load the test Excel file
        file_path = Path("tests/data") / filename
        with open(file_path, "rb") as f:
            file_content = f.read()

        # Get the import page
        res = testapp.get(url_for("batch.import_data"))

        # Find and submit the Třinec form
        # The form should be the second form on the page
        res = testapp.post(
            url_for("batch.prepare_data_from_trinec"),
            upload_files=[("trinec_file", filename, file_content)],
        )

        # Should redirect back to import page with success message
        assert res.status_code == 200
        assert "Soubor byl úspěšně načten" in res
        assert f"Nalezeno {valid_lines} řádků" in res
        assert f"({skipped_lines} řádků vynecháno)" in res

        # The form should be pre-filled with the data
        form = res.forms["importForm"]
        assert form["input_data"].value is not None
        assert len(form["input_data"].value.splitlines()) == valid_lines

        # Verify the data format is correct (semicolon-delimited)
        lines = form["input_data"].value.splitlines()
        assert "0407156596;DANIEL;DOLEŽAL" in lines[0]

    def test_prepare_trinec_no_file(self, user, testapp):
        """Test submitting Třinec form without file"""
        login(user, testapp)

        # Submit form without file
        res = testapp.post(
            url_for("batch.prepare_data_from_trinec"),
        ).follow()

        assert res.status_code == 200
        assert "Nebyl vybrán žádný soubor" in res

    def test_prepare_trinec_empty_filename(self, user, testapp):
        """Test submitting Třinec form with empty filename"""
        login(user, testapp)

        # Submit form with empty filename (file field present but no file selected)
        res = testapp.post(
            url_for("batch.prepare_data_from_trinec"),
            upload_files=[("trinec_file", "", b"")],
        ).follow()

        assert res.status_code == 200
        assert "Nebyl vybrán žádný soubor" in res

    def test_prepare_trinec_general_exception(self, user, testapp):
        """Test general exception handling in Třinec file processing"""
        login(user, testapp)

        # Create a file that will cause an error during processing
        # Send a valid XLSX file structure but with corrupted content
        # that will fail during openpyxl processing
        corrupted_content = (
            b"PK\x03\x04" + b"\x00" * 100  # Minimal ZIP header but corrupted
        )

        res = testapp.post(
            url_for("batch.prepare_data_from_trinec"),
            upload_files=[("trinec_file", "corrupted.xlsx", corrupted_content)],
        ).follow()

        assert res.status_code == 200
        assert "Při zpracování souboru došlo k chybě" in res

    def test_prepare_trinec_corrupted_table_header(self, user, testapp):
        """Test submitting Excel file with corrupted table header"""
        login(user, testapp)

        # Load the test Excel file
        file_path = Path("tests/data/trinec_import_corrupted_header.xlsx")
        with open(file_path, "rb") as f:
            file_content = f.read()

        # Submit the Třinec form
        res = testapp.post(
            url_for("batch.prepare_data_from_trinec"),
            upload_files=[
                (
                    "trinec_file",
                    "trinec_import_corrupted_header.xlsx",
                    file_content,
                )
            ],
        ).follow()

        assert res.status_code == 200
        assert "Při zpracování souboru došlo k chybě" in res
        assert "Sloupce se nepodařilo rozpoznat" in res

    @pytest.mark.parametrize(
        "filename,valid_lines,skipped_lines",
        (
            ("trinec_import_valid_tb.xlsx", 6, 1),
            ("trinec_import_valid_pb.xlsx", 4, 1),
            ("trinec_import_skip_lines.xlsx", 1, 6),
        ),
    )
    def test_prepare_trinec_full_workflow(
        self, user, testapp, filename, valid_lines, skipped_lines
    ):
        """Test complete workflow: upload Třinec file and import data"""
        login(user, testapp)

        # Load the test Excel file
        file_path = Path("tests/data/") / filename
        with open(file_path, "rb") as f:
            file_content = f.read()

        existing_records = Record.query.count()
        existing_batches = Batch.query.count()

        # Upload Třinec file
        res = testapp.post(
            url_for("batch.prepare_data_from_trinec"),
            upload_files=[("trinec_file", "trinec_import.xlsx", file_content)],
        )

        assert res.status_code == 200
        assert "Soubor byl úspěšně načten" in res

        # Now submit the pre-filled form to actually import the data
        form = res.forms["importForm"]
        # The donation_center_id should be pre-set to 3 (Třinec) by the view
        res = form.submit().follow()

        # Check that import was successful
        assert "Import proběhl úspěšně" in res
        assert res.status_code == 200

        # Verify records were created
        assert Record.query.count() == existing_records + valid_lines
        assert Batch.query.count() == existing_batches + 1

        # Verify the batch is associated with Třinec (donation_center_id=3)
        latest_batch = Batch.query.order_by(Batch.id.desc()).first()
        assert latest_batch.donation_center_id == 3

    def test_prepare_trinec_full_workflow_repairable_data(self, user, testapp):
        """Test complete workflow with repairable data"""
        login(user, testapp)

        # Load the test Excel file
        file_path = Path("tests/data/") / "trinec_import_repairable_data.xlsx"
        with open(file_path, "rb") as f:
            file_content = f.read()

        existing_records = Record.query.count()
        existing_batches = Batch.query.count()

        # Upload Třinec file
        res = testapp.post(
            url_for("batch.prepare_data_from_trinec"),
            upload_files=[
                ("trinec_file", "trinec_import_repairable_data.xlsx", file_content)
            ],
        )

        assert res.status_code == 200
        assert "Soubor byl úspěšně načten" in res

        # Now submit the pre-filled form to actually import the data
        form = res.forms["importForm"]
        # The donation_center_id should be pre-set to 3 (Třinec) by the view
        res = form.submit()

        form = res.forms["importForm"]

        assert "chybí PSČ, nahrazeno nulami" in form["invalid_lines_errors"].value
        assert (
            "chybí pojišťovna, nahrazena nulami" in form["invalid_lines_errors"].value
        )

        # Now submit the pre-filled form again with the fixed data
        res = form.submit().follow()

        # Check that import was successful
        assert "Import proběhl úspěšně" in res
        assert res.status_code == 200

        # Verify records were created
        assert Record.query.count() == existing_records + 6
        assert Batch.query.count() == existing_batches + 1

        # Verify the batch is associated with Třinec (donation_center_id=3)
        latest_batch = Batch.query.order_by(Batch.id.desc()).first()
        assert latest_batch.donation_center_id == 3
