# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""Tests for Sprint 5 skills: Stripe, Square."""

from unittest.mock import MagicMock, patch

HTTPX_GET = "familiar.skills._api_utils.httpx.get"
HTTPX_POST = "familiar.skills._api_utils.httpx.post"
CACHE_GET = "familiar.core.api_cache.get_cached"
CACHE_SET = "familiar.core.api_cache.set_cached"

STRIPE_ENV = {"STRIPE_SECRET_KEY": "sk_test_abc123"}
SQUARE_ENV = {"SQUARE_ACCESS_TOKEN": "sq_test_abc123", "SQUARE_LOCATION_ID": "LOC123"}


def _mock_response(data, status=200):
    resp = MagicMock()
    resp.status_code = status
    resp.json.return_value = data
    resp.content = b'{"ok": true}'
    resp.text = '{"ok": true}'
    return resp


def _stripe_env(key):
    return STRIPE_ENV.get(key, "")


def _square_env(key):
    return SQUARE_ENV.get(key, "")


# ── Stripe ──────────────────────────────────────────────────────────────


class TestCreatePaymentLink:
    def test_no_config(self):
        from familiar.skills.stripe_pay.skill import create_payment_link

        with patch("familiar.skills.stripe_pay.skill.get_env", return_value=""):
            result = create_payment_link({"name": "Widget", "amount": 25})
        assert "not configured" in result.lower()

    def test_no_name(self):
        from familiar.skills.stripe_pay.skill import create_payment_link

        with patch("familiar.skills.stripe_pay.skill.get_env", side_effect=_stripe_env):
            result = create_payment_link({"name": "", "amount": 25})
        assert "provide a product name" in result.lower()

    def test_success(self):
        from familiar.skills.stripe_pay.skill import create_payment_link

        resp = _mock_response(
            {
                "id": "plink_abc123",
                "url": "https://buy.stripe.com/test_abc123",
                "active": True,
            }
        )

        with (
            patch("familiar.skills.stripe_pay.skill.get_env", side_effect=_stripe_env),
            patch(HTTPX_POST, return_value=resp),
        ):
            result = create_payment_link({"name": "Art Print", "amount": 45.00})
        assert "plink_abc123" in result
        assert "buy.stripe.com" in result
        assert "$45.00" in result


class TestListPayments:
    def test_no_config(self):
        from familiar.skills.stripe_pay.skill import list_payments

        with patch("familiar.skills.stripe_pay.skill.get_env", return_value=""):
            result = list_payments({})
        assert "not configured" in result.lower()

    def test_success(self):
        from familiar.skills.stripe_pay.skill import list_payments

        resp = _mock_response(
            {
                "data": [
                    {
                        "id": "ch_abc123",
                        "amount": 5000,
                        "currency": "usd",
                        "status": "succeeded",
                        "description": "Art sale",
                        "customer": "cus_xyz",
                        "created": 1709740800,
                    },
                ],
                "has_more": False,
            }
        )

        with (
            patch("familiar.skills.stripe_pay.skill.get_env", side_effect=_stripe_env),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = list_payments({"limit": 10})
        assert "$50.00" in result
        assert "succeeded" in result
        assert "ch_abc123" in result


class TestCreateInvoice:
    def test_no_email(self):
        from familiar.skills.stripe_pay.skill import create_invoice

        with patch("familiar.skills.stripe_pay.skill.get_env", side_effect=_stripe_env):
            result = create_invoice({"customer_email": "", "amount": 100})
        assert "provide a customer_email" in result.lower()

    def test_no_amount(self):
        from familiar.skills.stripe_pay.skill import create_invoice

        with patch("familiar.skills.stripe_pay.skill.get_env", side_effect=_stripe_env):
            result = create_invoice({"customer_email": "test@test.com", "amount": 0})
        assert "provide a valid amount" in result.lower()

    def test_success(self):
        from familiar.skills.stripe_pay.skill import create_invoice

        # Mock sequence: find customer, create invoice, add item, finalize
        customer_resp = _mock_response({"data": [{"id": "cus_abc"}]})
        invoice_resp = _mock_response({"id": "inv_abc123"})
        item_resp = _mock_response({"id": "ii_abc"})
        finalized_resp = _mock_response(
            {
                "id": "inv_abc123",
                "status": "open",
                "hosted_invoice_url": "https://invoice.stripe.com/i/test",
            }
        )

        call_count = {"n": 0}
        responses = [customer_resp, invoice_resp, item_resp, finalized_resp]

        def _get_side_effect(*args, **kwargs):
            return customer_resp

        def _post_side_effect(*args, **kwargs):
            call_count["n"] += 1
            idx = min(call_count["n"], len(responses) - 1)
            return responses[idx]

        with (
            patch("familiar.skills.stripe_pay.skill.get_env", side_effect=_stripe_env),
            patch(HTTPX_GET, side_effect=_get_side_effect),
            patch(HTTPX_POST, side_effect=_post_side_effect),
        ):
            result = create_invoice(
                {
                    "customer_email": "jane@example.com",
                    "amount": 250,
                    "description": "Consulting",
                }
            )
        assert "inv_abc123" in result
        assert "jane@example.com" in result


class TestDonationLink:
    def test_no_org_name(self):
        from familiar.skills.stripe_pay.skill import donation_link

        with patch("familiar.skills.stripe_pay.skill.get_env", side_effect=_stripe_env):
            result = donation_link({"org_name": ""})
        assert "provide the organization name" in result.lower()

    def test_success(self):
        from familiar.skills.stripe_pay.skill import donation_link

        resp = _mock_response(
            {
                "id": "plink_don123",
                "url": "https://buy.stripe.com/donate_abc",
            }
        )

        with (
            patch("familiar.skills.stripe_pay.skill.get_env", side_effect=_stripe_env),
            patch(HTTPX_POST, return_value=resp),
        ):
            result = donation_link(
                {
                    "org_name": "Community Food Bank",
                    "suggested_amount": 50,
                }
            )
        assert "plink_don123" in result
        assert "Community Food Bank" in result
        assert "$50.00" in result


class TestRefundPayment:
    def test_no_charge_id(self):
        from familiar.skills.stripe_pay.skill import refund_payment

        with patch("familiar.skills.stripe_pay.skill.get_env", side_effect=_stripe_env):
            result = refund_payment({"charge_id": ""})
        assert "provide a charge_id" in result.lower()

    def test_success(self):
        from familiar.skills.stripe_pay.skill import refund_payment

        resp = _mock_response(
            {
                "id": "re_abc123",
                "amount": 2500,
                "currency": "usd",
                "status": "succeeded",
            }
        )

        with (
            patch("familiar.skills.stripe_pay.skill.get_env", side_effect=_stripe_env),
            patch(HTTPX_POST, return_value=resp),
        ):
            result = refund_payment({"charge_id": "ch_abc123"})
        assert "re_abc123" in result
        assert "$25.00" in result
        assert "succeeded" in result


class TestStripeTools:
    def test_tools_registered(self):
        from familiar.skills.stripe_pay.skill import TOOLS

        names = [t["name"] for t in TOOLS]
        assert "create_payment_link" in names
        assert "list_payments" in names
        assert "create_invoice" in names
        assert "donation_link" in names
        assert "refund_payment" in names

    def test_all_have_handlers(self):
        from familiar.skills.stripe_pay.skill import TOOLS

        for tool in TOOLS:
            assert callable(tool["handler"])


# ── Square ──────────────────────────────────────────────────────────────


class TestSquarePayments:
    def test_no_config(self):
        from familiar.skills.square.skill import square_payments

        with patch("familiar.skills.square.skill.get_env", return_value=""):
            result = square_payments({})
        assert "not configured" in result.lower()

    def test_success(self):
        from familiar.skills.square.skill import square_payments

        resp = _mock_response(
            {
                "payments": [
                    {
                        "id": "pay_abc123xyz",
                        "amount_money": {"amount": 3500, "currency": "USD"},
                        "status": "COMPLETED",
                        "source_type": "CARD",
                        "created_at": "2026-03-05T14:30:00Z",
                        "receipt_url": "https://squareup.com/receipt/abc",
                    },
                ],
            }
        )

        with (
            patch("familiar.skills.square.skill.get_env", side_effect=_square_env),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = square_payments({"limit": 10})
        assert "$35.00" in result
        assert "COMPLETED" in result
        assert "CARD" in result


class TestSquareCustomers:
    def test_success(self):
        from familiar.skills.square.skill import square_customers

        resp = _mock_response(
            {
                "customers": [
                    {
                        "id": "cust_abc123456789",
                        "given_name": "Jane",
                        "family_name": "Doe",
                        "email_address": "jane@example.com",
                        "phone_number": "+15551234567",
                        "created_at": "2026-01-15T10:00:00Z",
                    },
                ],
            }
        )

        with (
            patch("familiar.skills.square.skill.get_env", side_effect=_square_env),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = square_customers({})
        assert "Jane Doe" in result
        assert "jane@example.com" in result

    def test_empty(self):
        from familiar.skills.square.skill import square_customers

        resp = _mock_response({"customers": []})

        with (
            patch("familiar.skills.square.skill.get_env", side_effect=_square_env),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = square_customers({})
        assert "no customers" in result.lower()


class TestSquareInvoices:
    def test_no_location(self):
        from familiar.skills.square.skill import square_invoices

        env = {"SQUARE_ACCESS_TOKEN": "tok", "SQUARE_LOCATION_ID": ""}
        with patch("familiar.skills.square.skill.get_env", side_effect=lambda k: env.get(k, "")):
            result = square_invoices({})
        assert "location_id" in result.lower()

    def test_success(self):
        from familiar.skills.square.skill import square_invoices

        resp = _mock_response(
            {
                "invoices": [
                    {
                        "id": "inv_square_abc12345",
                        "status": "PAID",
                        "title": "Web Design Project",
                        "payment_requests": [
                            {
                                "computed_amount_money": {"amount": 150000, "currency": "USD"},
                            }
                        ],
                        "created_at": "2026-03-01T10:00:00Z",
                    },
                ],
            }
        )

        with (
            patch("familiar.skills.square.skill.get_env", side_effect=_square_env),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = square_invoices({})
        assert "Web Design Project" in result
        assert "PAID" in result
        assert "$1500.00" in result


class TestSquareCatalog:
    def test_success(self):
        from familiar.skills.square.skill import square_catalog

        resp = _mock_response(
            {
                "objects": [
                    {
                        "id": "item_abc123",
                        "type": "ITEM",
                        "item_data": {
                            "name": "Espresso",
                            "description": "Single shot espresso",
                            "variations": [
                                {
                                    "item_variation_data": {
                                        "price_money": {"amount": 350, "currency": "USD"},
                                    },
                                }
                            ],
                        },
                    },
                ],
            }
        )

        with (
            patch("familiar.skills.square.skill.get_env", side_effect=_square_env),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = square_catalog({})
        assert "Espresso" in result
        assert "$3.50" in result

    def test_empty(self):
        from familiar.skills.square.skill import square_catalog

        resp = _mock_response({"objects": []})

        with (
            patch("familiar.skills.square.skill.get_env", side_effect=_square_env),
            patch(HTTPX_GET, return_value=resp),
            patch(CACHE_GET, return_value=None),
            patch(CACHE_SET),
        ):
            result = square_catalog({})
        assert "no catalog items" in result.lower()


class TestSquareTools:
    def test_tools_registered(self):
        from familiar.skills.square.skill import TOOLS

        names = [t["name"] for t in TOOLS]
        assert "square_payments" in names
        assert "square_customers" in names
        assert "square_invoices" in names
        assert "square_catalog" in names

    def test_all_have_handlers(self):
        from familiar.skills.square.skill import TOOLS

        for tool in TOOLS:
            assert callable(tool["handler"])
