import gzip
from decimal import Decimal

import pytest

from ccma.services.gnucash_import import GnuCashImportError, list_accounts, list_transactions

SAMPLE_GNUCASH_XML = """<?xml version="1.0" encoding="utf-8"?>
<gnc-v2
    xmlns:gnc="http://www.gnucash.org/XML/gnc"
    xmlns:act="http://www.gnucash.org/XML/act"
    xmlns:book="http://www.gnucash.org/XML/book"
    xmlns:trn="http://www.gnucash.org/XML/trn"
    xmlns:ts="http://www.gnucash.org/XML/ts"
    xmlns:split="http://www.gnucash.org/XML/split">
<gnc:book version="2.0.0">
  <book:id type="guid">book-1</book:id>
  <gnc:account version="2.0.0">
    <act:name>Root Account</act:name>
    <act:id type="guid">root-guid</act:id>
    <act:type>ROOT</act:type>
  </gnc:account>
  <gnc:account version="2.0.0">
    <act:name>Aktiva</act:name>
    <act:id type="guid">assets-guid</act:id>
    <act:type>ASSET</act:type>
    <act:parent type="guid">root-guid</act:parent>
  </gnc:account>
  <gnc:account version="2.0.0">
    <act:name>Girokonto</act:name>
    <act:id type="guid">bank-guid</act:id>
    <act:type>BANK</act:type>
    <act:parent type="guid">assets-guid</act:parent>
  </gnc:account>
  <gnc:account version="2.0.0">
    <act:name>Beitraege</act:name>
    <act:id type="guid">income-guid</act:id>
    <act:type>INCOME</act:type>
    <act:parent type="guid">root-guid</act:parent>
  </gnc:account>
  <gnc:transaction version="2.0.0">
    <trn:id type="guid">tx-1</trn:id>
    <trn:date-posted><ts:date>2026-06-21 10:59:00 +0200</ts:date></trn:date-posted>
    <trn:description>Mitgliedsbeitrag Max Mustermann</trn:description>
    <trn:splits>
      <trn:split>
        <split:id type="guid">split-1a</split:id>
        <split:memo>Beitrag Juni</split:memo>
        <split:value>15000/100</split:value>
        <split:quantity>15000/100</split:quantity>
        <split:account type="guid">bank-guid</split:account>
      </trn:split>
      <trn:split>
        <split:id type="guid">split-1b</split:id>
        <split:memo></split:memo>
        <split:value>-15000/100</split:value>
        <split:quantity>-15000/100</split:quantity>
        <split:account type="guid">income-guid</split:account>
      </trn:split>
    </trn:splits>
  </gnc:transaction>
  <gnc:transaction version="2.0.0">
    <trn:id type="guid">tx-2</trn:id>
    <trn:date-posted><ts:date>2026-07-01 08:30:00 +0200</ts:date></trn:date-posted>
    <trn:description>Bankgebuehr</trn:description>
    <trn:splits>
      <trn:split>
        <split:id type="guid">split-2a</split:id>
        <split:memo></split:memo>
        <split:value>-500/100</split:value>
        <split:quantity>-500/100</split:quantity>
        <split:account type="guid">bank-guid</split:account>
      </trn:split>
      <trn:split>
        <split:id type="guid">split-2b</split:id>
        <split:memo></split:memo>
        <split:value>500/100</split:value>
        <split:quantity>500/100</split:quantity>
        <split:account type="guid">income-guid</split:account>
      </trn:split>
    </trn:splits>
  </gnc:transaction>
</gnc:book>
</gnc-v2>
"""


def _write_sample(tmp_path, *, gzipped: bool = False, suffix: str = ".gnucash"):
    path = tmp_path / f"test{suffix}"
    data = SAMPLE_GNUCASH_XML.encode("utf-8")
    if gzipped:
        data = gzip.compress(data)
    path.write_bytes(data)
    return path


def test_list_accounts_builds_hierarchical_full_names(tmp_path) -> None:
    path = _write_sample(tmp_path)
    accounts = list_accounts(path)
    by_guid = {account.guid: account for account in accounts}

    assert "root-guid" not in by_guid
    assert by_guid["bank-guid"].full_name == "Aktiva:Girokonto"
    assert by_guid["bank-guid"].account_type == "BANK"
    assert by_guid["assets-guid"].full_name == "Aktiva"
    assert by_guid["income-guid"].full_name == "Beitraege"


def test_list_transactions_returns_splits_for_selected_account(tmp_path) -> None:
    path = _write_sample(tmp_path)
    transactions = list_transactions(path, "bank-guid")

    assert [item.guid for item in transactions] == ["tx-1", "tx-2"]
    first = transactions[0]
    assert first.date.isoformat() == "2026-06-21"
    assert first.description == "Mitgliedsbeitrag Max Mustermann"
    assert first.memo == "Beitrag Juni"
    assert first.amount == Decimal("150.00")
    second = transactions[1]
    assert second.amount == Decimal("-5.00")


def test_list_transactions_only_includes_splits_on_the_requested_account(tmp_path) -> None:
    path = _write_sample(tmp_path)
    transactions = list_transactions(path, "income-guid")

    assert [item.guid for item in transactions] == ["tx-1", "tx-2"]
    assert transactions[0].amount == Decimal("-150.00")
    assert transactions[1].amount == Decimal("5.00")


def test_gzip_compressed_gnucash_file_is_read_transparently(tmp_path) -> None:
    path = _write_sample(tmp_path, gzipped=True, suffix=".gnucash")
    accounts = list_accounts(path)
    assert any(account.guid == "bank-guid" for account in accounts)
    transactions = list_transactions(path, "bank-guid")
    assert len(transactions) == 2


def test_invalid_xml_raises_gnucash_import_error(tmp_path) -> None:
    path = tmp_path / "broken.gnucash"
    path.write_text("not xml at all <<<", encoding="utf-8")
    with pytest.raises(GnuCashImportError):
        list_accounts(path)


def test_missing_file_raises_gnucash_import_error(tmp_path) -> None:
    with pytest.raises(GnuCashImportError):
        list_accounts(tmp_path / "does-not-exist.gnucash")


def test_file_without_book_raises_gnucash_import_error(tmp_path) -> None:
    path = tmp_path / "empty.gnucash"
    path.write_text('<?xml version="1.0"?><gnc-v2></gnc-v2>', encoding="utf-8")
    with pytest.raises(GnuCashImportError, match="gnc:book"):
        list_accounts(path)
