"""ListGlobally search source — meta-aggregator backing Properstar.

Endpoint discovered 2026-05-25 via probing: POST /api/v1/listings/search
returns 401 (not 404/405), meaning the endpoint EXISTS and accepts our body
but requires JWT auth. Same token as sources/properstar.py (.properstar_token).

STATUS: scaffolded but body schema unknown. Token currently 84d expired,
so live exploration is blocked on `python3 import_favorites.py --login` to
refresh credentials.

POST-REFRESH workflow:
1. Refresh token: `python3 import_favorites.py --login` (browser)
2. Try body shapes against /api/v1/listings/search (try {country, region,
   minPrice, maxPrice, page, pageSize} first — common REST convention)
3. Inspect response shape, map fields → PropertyHit
4. Populate _real_search() below

WHY THIS MATTERS:
- Same auth as Properstar (we already have the JWT flow)
- Bypasses per-platform bot blocks (Idealista/Immobiliare are aggregated here)
- Adds Spain/Portugal/Netherlands inventory without writing per-country scrapers
- Filters (price, size, bedrooms) likely pushable into the API payload upstream
"""
from __future__ import annotations

import sys
from pathlib import Path
from typing import Iterator

import requests

SCRIPT_DIR = Path(__file__).resolve().parent.parent
if str(SCRIPT_DIR) not in sys.path:
    sys.path.insert(0, str(SCRIPT_DIR))

from sources._base import PropertyHit, SearchCriteria, Source  # noqa: E402

TOKEN_PATH = SCRIPT_DIR / '.properstar_token'
SEARCH_URL = 'https://listings-api.listglobally.com/api/v1/listings/search'


class ListGloballySearchSource(Source):
    name = 'listglobally_search'
    countries = ['FR', 'IT', 'ES', 'PT', 'NL', 'BE', 'DE']
    requires_auth = True

    def _get_token(self) -> str | None:
        if not TOKEN_PATH.exists():
            return None
        t = TOKEN_PATH.read_text().strip()
        return t if t.startswith('eyJ') else None

    def health(self) -> tuple[bool, str]:
        token = self._get_token()
        if not token:
            return False, 'no token (run: python3 import_favorites.py --login)'

        # Cheap probe — POST with minimal body, expect 200 / 400 (schema err)
        try:
            r = requests.post(
                SEARCH_URL,
                headers={'Authorization': f'Bearer {token}',
                         'Accept': 'application/json',
                         'Content-Type': 'application/json',
                         'User-Agent': 'paradisomatch/1.0',
                         'Referer': 'https://www.properstar.nl/'},
                json={'country': 'FR', 'page': 1, 'pageSize': 1},
                timeout=10,
            )
            if r.status_code == 401:
                return False, 'token expired (run --login)'
            if r.status_code == 200:
                return True, 'authenticated'
            # 400 = body shape wrong, but auth worked — still usable for spike
            if r.status_code == 400:
                return True, f'auth OK, body shape needs tuning: {r.text[:80]}'
            return False, f'HTTP {r.status_code}'
        except requests.RequestException as e:
            return False, f'{type(e).__name__}'

    def search(self, criteria: SearchCriteria,
               known_urls: set[str] | None = None) -> Iterator[PropertyHit]:
        """STUB — needs body schema discovery post-token-refresh.

        Once schema is known, implement: build body from criteria, POST,
        iterate response['listings'] (or similar), yield PropertyHit per item.
        Use criteria.max_pages to paginate.
        """
        token = self._get_token()
        if not token:
            return  # health() already reported this; orchestrator skipped us

        # Stub implementation — yields nothing. Real iteration TBD.
        # When implemented, body shape is likely something like:
        # {'country': criteria.country, 'region': criteria.region,
        #  'minPrice': criteria.min_price, 'maxPrice': criteria.max_price,
        #  'minSurface': criteria.min_building_m2,
        #  'page': 1, 'pageSize': 50}
        return
        yield  # pragma: no cover — keeps this a generator
