import pytest

from imapsync_bulk.accounts import parse_accounts
from imapsync_bulk.csv_source import (
    Credential,
    CsvSourceError,
    load_csv,
    parse_csv,
    render_accounts_file,
)

BASIC = "E-mail,Password\nalice@example.com,motdepasse1\nbob@example.com,motdepasse2\n"


def test_parses_the_expected_headers():
    creds = parse_csv(BASIC)
    assert [c.email for c in creds] == ["alice@example.com", "bob@example.com"]
    assert [c.password for c in creds] == ["motdepasse1", "motdepasse2"]


@pytest.mark.parametrize(
    "header",
    [
        "E-mail,Password",
        "email,password",
        "EMAIL,PASSWORD",
        "  E-Mail  ,  Password  ",
        "Adresse e-mail,Mot de passe",
        "Mail,MDP",
    ],
)
def test_header_variants_are_accepted(header):
    creds = parse_csv(f"{header}\nalice@example.com,secret\n")
    assert creds[0].email == "alice@example.com"
    assert creds[0].password == "secret"


def test_semicolon_delimited_csv_from_french_excel():
    creds = parse_csv("E-mail;Password\nalice@example.com;secret\n")
    assert creds[0].email == "alice@example.com"


def test_tab_delimited_csv():
    creds = parse_csv("E-mail\tPassword\nalice@example.com\tsecret\n")
    assert creds[0].email == "alice@example.com"


def test_bom_from_excel_is_stripped(tmp_path):
    path = tmp_path / "comptes.csv"
    path.write_bytes(b"\xef\xbb\xbfE-mail,Password\r\nalice@example.com,secret\r\n")
    creds = load_csv(path)
    assert creds[0].email == "alice@example.com"


def test_extra_columns_are_ignored():
    creds = parse_csv("Nom,E-mail,Password,Quota\nAlice,alice@x.fr,secret,5G\n")
    assert creds[0].email == "alice@x.fr"
    assert creds[0].password == "secret"


def test_blank_lines_are_skipped():
    creds = parse_csv("E-mail,Password\nalice@x.fr,secret\n,\n\nbob@x.fr,secret2\n")
    assert len(creds) == 2


def test_missing_email_column_is_reported():
    with pytest.raises(CsvSourceError, match="E-mail"):
        parse_csv("Nom,Password\nAlice,secret\n")


def test_missing_password_column_is_reported():
    with pytest.raises(CsvSourceError, match="Password"):
        parse_csv("E-mail,Nom\nalice@x.fr,Alice\n")


def test_missing_password_value_names_the_account():
    with pytest.raises(CsvSourceError, match="alice@x.fr"):
        parse_csv("E-mail,Password\nalice@x.fr,\n")


def test_missing_email_value_is_reported():
    with pytest.raises(CsvSourceError, match="adresse manquante"):
        parse_csv("E-mail,Password\n,secret\n")


def test_duplicate_email_is_refused():
    with pytest.raises(CsvSourceError, match="apparait deja"):
        parse_csv("E-mail,Password\nalice@x.fr,a\nALICE@x.fr,b\n")


def test_empty_csv_is_refused():
    with pytest.raises(CsvSourceError, match="vide"):
        parse_csv("")


def test_csv_with_only_headers_is_refused():
    with pytest.raises(CsvSourceError, match="aucun compte"):
        parse_csv("E-mail,Password\n")


def test_semicolon_in_password_is_refused():
    """Le format imapsync n'a pas d'echappement : mieux vaut refuser."""
    with pytest.raises(CsvSourceError, match="separateur"):
        parse_csv('E-mail,Password\nalice@x.fr,"mot;de;passe"\n')


def test_semicolon_in_email_is_refused():
    with pytest.raises(CsvSourceError, match="separateur"):
        parse_csv('E-mail,Password\n"ali;ce@x.fr",secret\n')


def test_password_with_comma_survives_quoting():
    creds = parse_csv('E-mail,Password\nalice@x.fr,"mot,de,passe"\n')
    assert creds[0].password == "mot,de,passe"


def test_password_with_spaces_is_preserved():
    creds = parse_csv('E-mail,Password\nalice@x.fr,"mot de passe"\n')
    assert creds[0].password == "mot de passe"


# --- rendu du fichier de comptes -----------------------------------------


def _creds():
    return [
        Credential("alice@example.com", "secret1", 2),
        Credential("bob@example.com", "secret2", 3),
    ]


def test_render_uses_the_same_credentials_on_both_sides():
    text = render_accounts_file(_creds(), "old.example", "new.example", header=False)
    first = text.splitlines()[0]
    assert first == "old.example;alice@example.com;secret1;new.example;alice@example.com;secret1;;"


def test_render_output_is_reparsable_by_the_official_parser():
    text = render_accounts_file(_creds(), "old.example", "new.example", "--automap")
    accounts = parse_accounts(text)
    assert len(accounts) == 2
    assert accounts[0].host1 == "old.example"
    assert accounts[0].host2 == "new.example"
    assert accounts[0].user1 == accounts[0].user2 == "alice@example.com"
    assert accounts[0].extra == ["--automap"]


def test_render_without_extra_leaves_the_column_empty():
    accounts = parse_accounts(
        render_accounts_file(_creds(), "a", "b", "", header=False)
    )
    assert accounts[0].extra == []


def test_render_header_documents_the_hosts():
    text = render_accounts_file(_creds(), "old.example", "new.example")
    assert "# host1 = old.example" in text
    assert "# host2 = new.example" in text
    assert "chmod 600" in text


def test_render_refuses_a_host_with_a_separator():
    with pytest.raises(CsvSourceError, match="host1"):
        render_accounts_file(_creds(), "bad;host", "new.example")


def test_render_refuses_an_empty_host():
    with pytest.raises(CsvSourceError, match="host2"):
        render_accounts_file(_creds(), "old.example", "  ")
