"""
Smoke‑tests for the Phase‑1 HubSpot critical fixes.

All HTTP calls are intercepted with `aioresponses`, so the tests run fully
offline.  Run with:  pytest -v tests/test_hubspot_critical_fixes.py
"""

import asyncio
from unittest.mock import Mock, patch

import pandas as pd
import pytest
from aiohttp import FormData
from aioresponses import aioresponses

from fmatch.integrations.hubspot.core import HubSpotIntegration, HubSpotCredentials
from fmatch.integrations.hubspot.data_validation import HubSpotDataValidator
# Bulk API is imported only for the FormData test
from fmatch.integrations.hubspot.bulk_api import HubSpotBulkAPI


# --------------------------------------------------------------------------- #
# 1.  Rate‑limit header parsing
# --------------------------------------------------------------------------- #
class TestRateLimitHeaders:
    """Phase‑1: _update_rate_limits must parse HubSpot‑specific headers."""

    def _make_integration(self) -> HubSpotIntegration:
        creds = HubSpotCredentials(client_id="test", access_token="x", use_private_app=True)
        return HubSpotIntegration(creds)

    def test_parse_full_header_set(self):
        hs = self._make_integration()
        hs._update_rate_limits(
            {
                "X-HubSpot-RateLimit-Secondly-Remaining": "145",
                "X-HubSpot-RateLimit-Secondly": "150",
                "X-HubSpot-RateLimit-Daily-Remaining": "45000",
                "X-HubSpot-RateLimit-Daily": "50000",
            }
        )
        assert hs.api_limits.core_api_remaining == 145
        assert hs.api_limits.core_api_max == 150
        assert hs.api_limits.daily_remaining == 45000
        assert hs.api_limits.daily_max == 50000

    def test_parse_header_subset(self):
        """Dev/test portals omit daily limits – make sure we don’t crash."""
        hs = self._make_integration()
        hs._update_rate_limits(
            {
                "X-HubSpot-RateLimit-Secondly-Remaining": "100",
                "X-HubSpot-RateLimit-Secondly": "150",
            }
        )
        assert hs.api_limits.core_api_remaining == 100
        assert hs.api_limits.core_api_max == 150
        # Daily limits remain unset (None)
        assert hs.api_limits.daily_remaining is None
        assert hs.api_limits.daily_max is None


# --------------------------------------------------------------------------- #
# 2.  Multipart uploads must not carry a JSON Content‑Type
# --------------------------------------------------------------------------- #
@pytest.mark.asyncio
async def test_multipart_upload_strips_json_header():
    creds = HubSpotCredentials(client_id="x", access_token="token", use_private_app=True)
    hs = HubSpotIntegration(creds)

    # Create a dummy multipart form
    form = FormData()
    form.add_field("importRequest", '{"name": "test"}')
    form.add_field("files", b"id,name\n1,Alice\n", filename="test.csv")

    # Intercept *all* traffic
    with aioresponses() as mocked:
        mocked.post(
            "https://api.hubapi.com/crm/v3/imports",
            status=200,
            payload={"id": "import_123"},
        )
        mocked.get(
            "https://api.hubapi.com/integrations/v1/me",
            status=200,
            payload={},  # minimal stub so hs.connect() passes
        )

        await hs.connect()  # opens the ClientSession with auth header

        _, status = await hs._execute_api_request("POST", "/crm/v3/imports", data=form)
        assert status == 200

        # Inspect the sole outbound request
        req = mocked.requests[("POST", "https://api.hubapi.com/crm/v3/imports")][0][1]
        hdrs = req["headers"]
        assert "application/json" not in hdrs.get("Content-Type", "")

        await hs.disconnect()


# --------------------------------------------------------------------------- #
# 3.  OAuth dialog stores real tokens
# --------------------------------------------------------------------------- #
@patch("webbrowser.open")  # Prevent browser launch
@patch("fmatch.integrations.hubspot.gui_integration.HubSpotIntegration")
def test_gui_oauth_flow_stores_tokens(mock_integration_cls, _mock_browser):
    """
    The updated _run_connection() must place refreshed credentials
    into dialog.result after a successful OAuth round‑trip.
    """
    # Arrange: stub HubSpotIntegration.authenticate_oauth() -> True
    async def _fake_auth():
        return True

    fake_integr = Mock()
    fake_integr.authenticate_oauth = _fake_auth
    fake_integr.credentials = HubSpotCredentials(
        client_id="cid",
        access_token="access123",
        refresh_token="refresh123",
        portal_id="99999",
        user_email="user@example.com",
        use_private_app=False,
    )
    mock_integration_cls.return_value = fake_integr

    import tkinter as tk
    from fmatch.integrations.hubspot.gui_integration import HubSpotConnectionDialog

    root = tk.Tk()
    root.withdraw()  # Hide main window during test

    dlg = HubSpotConnectionDialog(root)
    dlg.client_id_var.set("cid")
    dlg._run_connection()  # synchronous helper inside dialog

    assert dlg.result.access_token == "access123"
    assert dlg.result.refresh_token == "refresh123"
    assert dlg.result.portal_id == "99999"

    dlg.destroy()
    root.destroy()


# --------------------------------------------------------------------------- #
# 4.  Property schema extraction pulls maxLength, not displayOrder
# --------------------------------------------------------------------------- #
@pytest.mark.asyncio
async def test_property_schema_max_length():
    creds = HubSpotCredentials(client_id="x", access_token="token", use_private_app=True)
    hs = HubSpotIntegration(creds)

    with aioresponses() as mocked:
        mocked.get(
            "https://api.hubapi.com/integrations/v1/me",
            status=200,
            payload={},
        )
        mocked.get(
            "https://api.hubapi.com/crm/v3/properties/contacts",
            status=200,
            payload={
                "results": [
                    {
                        "name": "firstname",
                        "label": "First Name",
                        "type": "string",
                        "fieldType": "text",
                        "maxLength": 100,
                    },
                    {
                        "name": "age",
                        "label": "Age",
                        "type": "number",
                        "fieldType": "number",
                    },
                ]
            },
        )

        await hs.connect()
        props = await hs.get_object_properties("contacts")
        await hs.disconnect()

    fname = next(p for p in props if p.name == "firstname")
    age = next(p for p in props if p.name == "age")

    assert fname.max_length == 100
    assert age.max_length is None


# --------------------------------------------------------------------------- #
# 5.  Duplicate detection must ignore empty composite keys
# --------------------------------------------------------------------------- #
def test_duplicate_group_skip_empty():
    creds = HubSpotCredentials(client_id="x", access_token="token", use_private_app=True)
    validator = HubSpotDataValidator(HubSpotIntegration(creds))

    df = pd.DataFrame(
        {
            "id": [1, 2, 3, 4],
            "email": ["", None, "", "user@example.com"],
        }
    )
    groups = validator._find_duplicate_groups(df, ["email"])
    assert groups == []  # no dupes with empty keys

    df2 = pd.DataFrame(
        {
            "id": [1, 2, 3, 4],
            "email": ["dup@example.com", "dup@example.com", "", "other@example.com"],
        }
    )
    groups2 = validator._find_duplicate_groups(df2, ["email"])
    assert len(groups2) == 1 and set(groups2[0]) == {0, 1}
