from __future__ import annotations

import json

from aegro.cli import app


class TestFinancialBill:
    def test_get_bill(self, runner, mock_settings, mock_client, temp_state):
        mock_client.get.return_value = {"_id": "b1", "description": "Conta"}
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(app, ["financial", "bill", "b1"])
        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["description"] == "Conta"
        mock_client.get.assert_called_once_with("/pub/v1/bills/b1", api_key="key-1")

    def test_get_bill_not_found(self, runner, mock_settings, mock_client, temp_state):
        mock_client.get.return_value = None
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(app, ["financial", "bill", "missing"])
        assert result.exit_code == 0
        assert "nao encontrad" in result.stdout.lower()

    def test_get_bill_no_farm(self, runner, mock_settings, temp_state):
        result = runner.invoke(app, ["financial", "bill", "b1"])
        assert result.exit_code != 0


class TestFinancialInstallment:
    def test_get_installment(self, runner, mock_settings, mock_client, temp_state):
        mock_client.get.return_value = {"_id": "i1", "dueDate": "2025-06-01"}
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(app, ["financial", "installment", "i1"])
        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["dueDate"] == "2025-06-01"
        mock_client.get.assert_called_once_with("/pub/v1/installments/i1", api_key="key-1")

    def test_get_installment_not_found(self, runner, mock_settings, mock_client, temp_state):
        mock_client.get.return_value = None
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(app, ["financial", "installment", "missing"])
        assert result.exit_code == 0
        assert "nao encontrad" in result.stdout.lower()

    def test_get_installment_no_farm(self, runner, mock_settings, temp_state):
        result = runner.invoke(app, ["financial", "installment", "i1"])
        assert result.exit_code != 0


class TestFinancialListInstallments:
    def test_list_no_filters(self, runner, mock_settings, mock_client, temp_state):
        mock_client.post.return_value = [{"_id": "i1", "dueDate": "2025-06-01"}]
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(app, ["financial", "installments"])
        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert len(data) == 1
        call_body = mock_client.post.call_args.kwargs["json_body"]
        assert call_body["requiredPageNumber"] == 1
        assert call_body["maximumItemsPerPageCount"] == 50

    def test_list_with_filters(self, runner, mock_settings, mock_client, temp_state):
        mock_client.post.return_value = []
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(
            app,
            [
                "financial",
                "installments",
                "--operation-type",
                "EXPENSE",
                "--status",
                "PAID",
                "--status",
                "NOT_PAID",
                "--due-date-start",
                "2025-01-01",
                "--due-date-end",
                "2025-12-31",
                "--bill-key",
                "b1",
                "--bill-key",
                "b2",
            ],
        )
        assert result.exit_code == 0
        call_body = mock_client.post.call_args.kwargs["json_body"]
        assert call_body["operationType"] == "EXPENSE"
        assert call_body["statuses"] == ["PAID", "NOT_PAID"]
        assert call_body["dueDateStart"] == "2025-01-01"
        assert call_body["dueDateEnd"] == "2025-12-31"
        assert call_body["billKeys"] == ["b1", "b2"]

    def test_list_with_page(self, runner, mock_settings, mock_client, temp_state):
        mock_client.post.return_value = []
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        runner.invoke(app, ["financial", "installments", "--page", "3"])
        call_body = mock_client.post.call_args.kwargs["json_body"]
        assert call_body["requiredPageNumber"] == 3

    def test_list_empty_result(self, runner, mock_settings, mock_client, temp_state):
        mock_client.post.return_value = None
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(app, ["financial", "installments"])
        assert result.exit_code == 0
        assert "nenhum" in result.stdout.lower()


class TestFinancialCreateInstallment:
    def test_create_installment(self, runner, mock_settings, mock_client, temp_state):
        mock_client.post.return_value = {"_id": "i-new", "dueDate": "2025-06-01"}
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(
            app,
            [
                "financial",
                "create-installment",
                "--bill-key",
                "b1",
                "--bank-account-key",
                "ba1",
                "--due-date",
                "2025-06-01",
                "--amount",
                "1500.00",
            ],
        )
        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["_id"] == "i-new"
        call_body = mock_client.post.call_args.kwargs["json_body"]
        assert call_body["billKey"] == "b1"
        assert call_body["bankAccountKey"] == "ba1"
        assert call_body["dueDate"] == "2025-06-01"
        assert call_body["amount"]["amount"] == 1500.00
        assert call_body["amount"]["currency"] == "BRL"

    def test_create_installment_custom_currency(
        self, runner, mock_settings, mock_client, temp_state
    ):
        mock_client.post.return_value = None
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(
            app,
            [
                "financial",
                "create-installment",
                "--bill-key",
                "b1",
                "--bank-account-key",
                "ba1",
                "--due-date",
                "2025-06-01",
                "--amount",
                "500",
                "--currency",
                "USD",
            ],
        )
        assert result.exit_code == 0
        assert "Parcela criada" in result.stdout
        call_body = mock_client.post.call_args.kwargs["json_body"]
        assert call_body["amount"]["currency"] == "USD"

    def test_create_installment_no_farm(self, runner, mock_settings, temp_state):
        result = runner.invoke(
            app,
            [
                "financial",
                "create-installment",
                "--bill-key",
                "b1",
                "--bank-account-key",
                "ba1",
                "--due-date",
                "2025-06-01",
                "--amount",
                "100",
            ],
        )
        assert result.exit_code != 0


class TestFinancialUpdateInstallment:
    def test_update_installment(self, runner, mock_settings, mock_client, temp_state):
        mock_client.put.return_value = {"_id": "i1", "status": "PAID"}
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(
            app,
            [
                "financial",
                "update-installment",
                "i1",
                "--bill-key",
                "b1",
                "--bank-account-key",
                "ba1",
                "--number",
                "1",
                "--due-date",
                "2025-06-01",
                "--amount",
                "2000.00",
                "--status",
                "PAID",
            ],
        )
        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data["_id"] == "i1"
        mock_client.put.assert_called_once()
        call_body = mock_client.put.call_args.kwargs["json_body"]
        assert call_body["key"] == "i1"
        assert call_body["billKey"] == "b1"
        assert call_body["number"] == 1
        assert call_body["status"] == "PAID"

    def test_update_installment_with_realized(self, runner, mock_settings, mock_client, temp_state):
        mock_client.put.return_value = None
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(
            app,
            [
                "financial",
                "update-installment",
                "i1",
                "--bill-key",
                "b1",
                "--bank-account-key",
                "ba1",
                "--number",
                "1",
                "--due-date",
                "2025-06-01",
                "--amount",
                "1000",
                "--realized-amount",
                "950",
                "--realized-date",
                "2025-05-30",
            ],
        )
        assert result.exit_code == 0
        assert "atualizada" in result.stdout.lower()
        call_body = mock_client.put.call_args.kwargs["json_body"]
        assert call_body["realizedDate"] == "2025-05-30"
        assert call_body["realizedAmount"]["amount"] == 950.0
        assert call_body["realizedAmount"]["currency"] == "BRL"


class TestFinancialDeleteInstallment:
    def test_delete_installment(self, runner, mock_settings, mock_client, temp_state):
        mock_client.delete.return_value = None
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(app, ["financial", "delete-installment", "i1"])
        assert result.exit_code == 0
        assert "excluida" in result.stdout.lower()
        assert "Fazenda Sul" in result.stdout
        mock_client.delete.assert_called_once_with("/pub/v1/installments/i1", api_key="key-1")

    def test_delete_installment_no_farm(self, runner, mock_settings, temp_state):
        result = runner.invoke(app, ["financial", "delete-installment", "i1"])
        assert result.exit_code != 0


class TestFinancialRealize:
    def test_realize_installments(self, runner, mock_settings, mock_client, temp_state):
        mock_client.post.return_value = [{"_id": "i1", "status": "PAID"}]
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(app, ["financial", "realize", "--key", "i1", "--key", "i2"])
        assert result.exit_code == 0
        data = json.loads(result.stdout)
        assert data[0]["_id"] == "i1"
        call_body = mock_client.post.call_args.kwargs["json_body"]
        assert call_body["list"] == ["i1", "i2"]
        assert mock_client.post.call_args.args[0] == "/pub/v1/installments/realizeList"

    def test_realize_no_result(self, runner, mock_settings, mock_client, temp_state):
        mock_client.post.return_value = None
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(app, ["financial", "realize", "--key", "i1"])
        assert result.exit_code == 0
        assert "realizadas" in result.stdout.lower()

    def test_realize_no_farm(self, runner, mock_settings, temp_state):
        result = runner.invoke(app, ["financial", "realize", "--key", "i1"])
        assert result.exit_code != 0


class TestFinancialBillsList:
    def test_list_defaults(self, runner, mock_settings, mock_client, temp_state):
        mock_client.post.return_value = []
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(app, ["financial", "bills"])
        assert result.exit_code == 0
        call = mock_client.post.call_args
        assert call.args[0] == "/pub/v1/bills/filter"
        assert call.kwargs["json_body"]["requiredPageNumber"] == 1

    def test_list_filters(self, runner, mock_settings, mock_client, temp_state):
        mock_client.post.return_value = []
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        runner.invoke(
            app,
            [
                "financial",
                "bills",
                "--operation-type",
                "EXPENSE",
                "--start-date",
                "2025-01-01",
                "--end-date",
                "2025-12-31",
                "--company-key",
                "c1",
                "--financial-category-key",
                "fc1",
                "--bank-account-key",
                "ba1",
                "--payment-method",
                "INSTALLMENT",
                "--receipt",
                "NF-100",
            ],
        )
        body = mock_client.post.call_args.kwargs["json_body"]
        assert body["operationType"] == "EXPENSE"
        assert body["startDate"] == "2025-01-01"
        assert body["endDate"] == "2025-12-31"
        assert body["companyKeys"] == ["c1"]
        assert body["financialCategoryKeys"] == ["fc1"]
        assert body["bankAccountKeys"] == ["ba1"]
        assert body["paymentMethods"] == ["INSTALLMENT"]
        assert body["receipt"] == "NF-100"


class TestFinancialBillCreate:
    def test_create_minimal(self, runner, mock_settings, mock_client, temp_state):
        mock_client.post.return_value = {"_id": "bill-new"}
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(
            app,
            [
                "financial",
                "create-bill",
                "--farm-key",
                "farm::f1",
                "--description",
                "Compra fert",
                "--entry-date",
                "2025-06-01",
                "--cash-flow",
                "EXPENSE",
                "--payment-method",
                "PROMPT",
                "--financial-category-key",
                "fc1",
                "--total-amount",
                "1500.00",
            ],
        )
        assert result.exit_code == 0
        call = mock_client.post.call_args
        assert call.args[0] == "/pub/v1/bills"
        body = call.kwargs["json_body"]
        assert body["farmKey"] == "farm::f1"
        assert body["cashFlow"] == "EXPENSE"
        assert body["paymentMethod"] == "PROMPT"
        assert body["totalAmount"] == {"amount": 1500.0, "currency": "BRL"}

    def test_create_with_optionals(self, runner, mock_settings, mock_client, temp_state):
        mock_client.post.return_value = {"_id": "bill-new"}
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        runner.invoke(
            app,
            [
                "financial",
                "create-bill",
                "--farm-key",
                "farm::f1",
                "--description",
                "Compra",
                "--entry-date",
                "2025-06-01",
                "--cash-flow",
                "EXPENSE",
                "--payment-method",
                "INSTALLMENT",
                "--financial-category-key",
                "fc1",
                "--total-amount",
                "2000.00",
                "--currency",
                "USD",
                "--bank-account-key",
                "ba1",
                "--company-key",
                "comp1",
                "--receipt",
                "NF-100",
                "--nfe-access-key",
                "35250512345678000190550010000123451234567890",
                "--tag",
                "insumo",
                "--installments",
                '[{"number":1,"dueDate":"2025-07-01","amount":{"amount":2000,"currency":"USD"}}]',
                "--inputs",
                '[{"elementKey":"element::e1","quantity":1,"measuringUnit":"kg"}]',
            ],
        )
        body = mock_client.post.call_args.kwargs["json_body"]
        assert body["bankAccountKey"] == "ba1"
        assert body["companyKey"] == "comp1"
        assert body["receipt"] == "NF-100"
        assert body["nfeAccessKey"].startswith("352505")
        assert body["tags"] == ["insumo"]
        assert body["installments"][0]["number"] == 1
        assert body["inputs"][0]["elementKey"] == "element::e1"

    def test_create_invalid_json(self, runner, mock_settings, mock_client, temp_state):
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(
            app,
            [
                "financial",
                "create-bill",
                "--farm-key",
                "farm::f1",
                "--description",
                "X",
                "--entry-date",
                "2025-06-01",
                "--cash-flow",
                "EXPENSE",
                "--payment-method",
                "PROMPT",
                "--financial-category-key",
                "fc1",
                "--total-amount",
                "10.00",
                "--installments",
                "not-json",
            ],
        )
        assert result.exit_code == 4

    def test_create_installments_not_list(self, runner, mock_settings, mock_client, temp_state):
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(
            app,
            [
                "financial",
                "create-bill",
                "--farm-key",
                "farm::f1",
                "--description",
                "X",
                "--entry-date",
                "2025-06-01",
                "--cash-flow",
                "EXPENSE",
                "--payment-method",
                "PROMPT",
                "--financial-category-key",
                "fc1",
                "--total-amount",
                "10.00",
                "--installments",
                '{"not":"list"}',
            ],
        )
        assert result.exit_code == 4


class TestFinancialBillUpdate:
    def test_update(self, runner, mock_settings, mock_client, temp_state):
        mock_client.put.return_value = {"_id": "bill::abc"}
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(
            app,
            [
                "financial",
                "update-bill",
                "bill::abc",
                "--body",
                '{"farmKey":"farm::f1","description":"X","entryDate":"2025-06-01","cashFlow":"EXPENSE","paymentMethod":"PROMPT","financialCategoryKey":"fc1","totalAmount":{"amount":10,"currency":"BRL"}}',
            ],
        )
        assert result.exit_code == 0
        call = mock_client.put.call_args
        assert call.args[0] == "/pub/v1/bills/bill::abc"
        assert call.kwargs["json_body"]["farmKey"] == "farm::f1"

    def test_update_non_object(self, runner, mock_settings, mock_client, temp_state):
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(
            app,
            ["financial", "update-bill", "bill::abc", "--body", "[]"],
        )
        assert result.exit_code == 4


class TestFinancialCloses:
    def test_get_closes(self, runner, mock_settings, mock_client, temp_state):
        mock_client.get.return_value = {"lastClosedDate": "2025-01-31"}
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(app, ["financial", "closes"])
        assert result.exit_code == 0
        mock_client.get.assert_called_once_with("/pub/v1/financial-closes", api_key="key-1")

    def test_get_closes_empty(self, runner, mock_settings, mock_client, temp_state):
        mock_client.get.return_value = None
        runner.invoke(app, ["farms", "select", "Fazenda Sul"])
        result = runner.invoke(app, ["financial", "closes"])
        assert result.exit_code == 0
        assert "nenhum" in result.stdout.lower()
