"""Mock-based tests for the read-only Odoo client (no live Odoo).

They lock in the domain the client builds, the invoice mapping, and — most
importantly — that the client refuses any mutating method. Skipped when httpx
isn't installed, so the openpyxl-only core suite still runs standalone.
"""
from __future__ import annotations

from datetime import date

import pytest

pytest.importorskip("httpx")

from app.odoo.client import (  # noqa: E402  (after importorskip by design)
    JsonRpcOdooClient,
    OcaRestOdooClient,
    OdooConfigError,
    _month_bounds,
    _parse_software_filter,
    build_odoo_client,
)


def make_client(**kw) -> JsonRpcOdooClient:
    return JsonRpcOdooClient(url="http://odoo.test", db="d", username="u", api_key="k", **kw)


def test_parse_software_filter():
    assert _parse_software_filter("journal:Software") == ("journal", "Software")
    assert _parse_software_filter("category:Subscriptions") == ("category", "Subscriptions")
    assert _parse_software_filter("analytic:SW") == ("analytic", "SW")
    # No kind prefix defaults to journal.
    assert _parse_software_filter("Software") == ("journal", "Software")


def test_month_bounds_including_leap():
    assert _month_bounds(2026, 10) == ("2026-10-01", "2026-10-31")
    assert _month_bounds(2024, 2) == ("2024-02-01", "2024-02-29")


def test_missing_credentials_raise():
    with pytest.raises(OdooConfigError):
        JsonRpcOdooClient(url="", db="", username="", api_key="")


def test_software_domain_variants():
    assert make_client(software_filter="journal:Software")._software_domain() == [
        ["journal_id.name", "=", "Software"]
    ]
    assert make_client(software_filter="category:Subs")._software_domain() == [
        ["invoice_line_ids.product_id.categ_id.name", "=", "Subs"]
    ]
    analytic = make_client(software_filter="analytic:SW")._software_domain()
    assert analytic[0][1] == "ilike"
    with pytest.raises(OdooConfigError):
        make_client(software_filter="bogus:x")._software_domain()


def test_read_only_guard_blocks_mutations():
    c = make_client()
    for bad in ("write", "create", "unlink"):
        with pytest.raises(PermissionError):
            c._execute("account.move", bad, [[]])


def test_fetch_software_invoices_builds_domain_and_maps(monkeypatch):
    c = make_client(software_filter="journal:Software")
    captured: dict = {}
    rows = [
        {"id": 5, "name": "INV/1", "partner_id": [101, "Acme"], "invoice_date": "2026-10-15",
         "amount_untaxed": 1000.0, "amount_total": 1150.0, "is_move_sent": True},
        {"id": 6, "name": "INV/2", "partner_id": False, "invoice_date": "2026-10-20",
         "amount_untaxed": 500, "amount_total": 575, "is_move_sent": False},
    ]

    def fake_execute(model, method, *params):
        captured["model"], captured["method"], captured["params"] = model, method, params
        return rows

    monkeypatch.setattr(c, "_execute", fake_execute)
    out = c.fetch_software_invoices(2026, 10)

    assert captured["model"] == "account.move"
    assert captured["method"] == "search_read"
    # _execute(model, method, [domain], {"fields": ...}) -> params[0] is [domain]
    domain = captured["params"][0][0]
    for clause in (
        ["move_type", "=", "out_invoice"],
        ["state", "=", "posted"],
        ["invoice_date", ">=", "2026-10-01"],
        ["invoice_date", "<=", "2026-10-31"],
        ["journal_id.name", "=", "Software"],
    ):
        assert clause in domain

    assert out[0].odoo_id == 5
    assert out[0].customer == "Acme"
    assert out[0].odoo_customer_id == 101
    assert out[0].invoice_date == date(2026, 10, 15)
    assert out[0].amount_untaxed == 1000.0
    assert out[0].sent is True
    # partner False -> no id, blank name
    assert out[1].odoo_customer_id is None
    assert out[1].customer == ""
    assert out[1].sent is False


def test_check_connection_reports_counts(monkeypatch):
    c = make_client(software_filter="journal:Software")
    monkeypatch.setattr(c, "_login", lambda: 7)
    calls: list = []

    def fake_execute(model, method, *params):
        calls.append((model, method))
        return 3 if len(calls) == 1 else 2  # posted count, then software count

    monkeypatch.setattr(c, "_execute", fake_execute)
    res = c.check_connection(2026, 10)
    assert res["authenticated"] is True
    assert res["uid"] == 7
    assert res["posted_out_invoices"] == 3
    assert res["software_invoices"] == 2
    assert res["filter_kind"] == "journal"
    assert res["month"] == "2026-10"


def test_build_oca_rest_stub_raises():
    class S:
        odoo_api_style = "oca_rest"

    client = build_odoo_client(S())
    assert isinstance(client, OcaRestOdooClient)
    with pytest.raises(NotImplementedError):
        client.fetch_software_invoices(2026, 10)
    with pytest.raises(NotImplementedError):
        client.check_connection(2026, 10)


def test_build_jsonrpc_default():
    class S:
        odoo_api_style = "jsonrpc"
        odoo_url = "http://x"
        odoo_db = "d"
        odoo_username = "u"
        odoo_api_key = "k"
        odoo_software_filter = "journal:Software"

    client = build_odoo_client(S())
    assert isinstance(client, JsonRpcOdooClient)
    client.close()
