# Thin client shim (Python) - contract-only helper
# Requires requests. Set API_BASE or rely on default.

import os
import re
import time
import requests

API_BASE = os.getenv('API_BASE', 'https://api.g3nretailstack.com')
DEFAULT_TIMEOUT_SECONDS = 15


def normalize_service_name(service):
    if not service:
        return None
    value = str(service).lower().lstrip('/')
    return value.split('/')[0]


def infer_service_from_path(path):
    if not path:
        return None
    match = re.match(r'^/(uas|usm|ofm|mrs|pvm|pmc)\b', str(path).lower())
    return match.group(1) if match else None


def resolve_auth_placement(service, path):
    if service == 'usm':
        if path and re.match(r'^/usm/api_key/validate\b', str(path), re.IGNORECASE):
            return 'header-api-key'
        return 'body'
    return 'header'


def resolve_org_placement(service, method, orgcode_placement=None):
    placement = orgcode_placement or 'auto'
    if placement != 'auto':
        return placement
    if service in ('pvm', 'pmc'):
        return 'header'
    if service == 'mrs':
        return 'query' if method == 'GET' else 'body'
    if service == 'ofm':
        return 'body'
    return 'none'


def build_headers(session_guid=None, api_key=None, orgcode=None, cccode=None, extra_headers=None):
    headers = dict(extra_headers or {})
    if session_guid:
        headers['x-session-guid'] = session_guid
    if api_key:
        headers['x-api-key'] = api_key
    if orgcode:
        headers['x-orgcode'] = orgcode
    if cccode:
        headers['x-cccode'] = cccode
    return headers


def apply_service_context(service, method, path, context, body=None, query=None, headers=None):
    svc = normalize_service_name(service) or infer_service_from_path(path)
    http_method = str(method or 'GET').upper()
    auth_placement = resolve_auth_placement(svc, path)
    org_placement = resolve_org_placement(svc, http_method, context.get('orgcode_placement'))

    next_headers = dict(headers or {})
    next_headers.update(context.get('extra_headers') or {})
    next_body = dict(body or {}) if body is not None else None
    next_query = dict(query or {}) if query is not None else None

    if context.get('cccode'):
        next_headers['x-cccode'] = context.get('cccode')
    if org_placement == 'header' and context.get('orgcode'):
        next_headers['x-orgcode'] = context.get('orgcode')
    if org_placement == 'query' and context.get('orgcode'):
        next_query = next_query or {}
        if 'orgcode' not in next_query:
            next_query['orgcode'] = context.get('orgcode')
    if org_placement == 'body' and context.get('orgcode'):
        next_body = next_body or {}
        if 'orgcode' not in next_body:
            next_body['orgcode'] = context.get('orgcode')

    if auth_placement == 'header':
        if context.get('session_guid'):
            next_headers['x-session-guid'] = context.get('session_guid')
        if context.get('api_key'):
            next_headers['x-api-key'] = context.get('api_key')
    elif auth_placement == 'header-api-key':
        if context.get('api_key'):
            next_headers['x-api-key'] = context.get('api_key')
    elif auth_placement == 'body':
        next_body = next_body or {}
        if context.get('session_guid') and 'session_guid' not in next_body:
            next_body['session_guid'] = context.get('session_guid')
        if context.get('api_key') and 'api_key' not in next_body:
            next_body['api_key'] = context.get('api_key')

    return {'headers': next_headers, 'body': next_body, 'query': next_query}


def request_json(
    method='GET',
    path=None,
    query=None,
    body=None,
    auth=None,
    auth_in_body=False,
    service=None,
    context=None,
    timeout_seconds=DEFAULT_TIMEOUT_SECONDS,
    retries=0,
    retry_delay_seconds=0.2,
    allow_retry=False,
):
    if not path:
        raise ValueError('path is required')

    method = str(method).upper()
    if method == 'GET' and body:
        raise ValueError('GET requests cannot include JSON bodies')

    auth = auth or {}
    headers = build_headers(
        session_guid=auth.get('session_guid'),
        api_key=auth.get('api_key'),
        orgcode=auth.get('orgcode'),
        cccode=auth.get('cccode'),
        extra_headers=auth.get('extra_headers'),
    )

    resolved_body = body
    resolved_query = query
    resolved_auth_in_body = auth_in_body
    if context:
        applied = apply_service_context(
            service=context.get('service') or service,
            method=method,
            path=path,
            context=context,
            body=body,
            query=query,
            headers=auth.get('extra_headers'),
        )
        headers = applied['headers']
        resolved_body = applied['body']
        resolved_query = applied['query']
        resolved_auth_in_body = False

    attempt = 0
    while True:
        attempt += 1
        try:
            url = f"{API_BASE}{path}"
            if method == 'GET':
                res = requests.get(url, params=resolved_query, headers=headers, timeout=timeout_seconds)
            else:
                payload = dict(resolved_body or {})
                if resolved_auth_in_body:
                    if auth.get('session_guid'):
                        payload['session_guid'] = auth.get('session_guid')
                    if auth.get('api_key'):
                        payload['api_key'] = auth.get('api_key')
                headers['content-type'] = 'application/json'
                res = requests.post(url, json=payload, headers=headers, timeout=timeout_seconds)
            data = res.json()
            data['status'] = res.status_code
            return data
        except Exception:
            is_retryable = method == 'GET' or allow_retry
            if attempt > retries or not is_retryable:
                raise
            time.sleep(retry_delay_seconds * attempt)


def assert_success(envelope):
    if envelope and envelope.get('success'):
        return envelope.get('data')
    raise RuntimeError(f"Request failed: {envelope.get('error') if envelope else 'unknown error'}")


# Notes:
# - Treat 404 as ambiguous (not found OR not associated). Use troubleshooting self-checks.
# - Prefer retries only for idempotent calls (GET) unless the endpoint supports idempotency keys.
# - For USM session/api-key calls, use context={'service': 'usm', 'session_guid': '...'}.
