"""Read-only paper account and crypto feed. No broker order methods exist."""
import json
import os
from datetime import datetime, timezone
from decimal import Decimal, InvalidOperation
import re
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode, urlsplit
from urllib.request import HTTPRedirectHandler, Request, build_opener
from engine import Rejected, number, instant

PAPER = 'https://paper-api.alpaca.markets'
DATA = 'https://data.alpaca.markets'
QUOTE_SYMBOLS = ('BTC/USD','ETH/USD','SOL/USD')

class NoRedirect(HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        raise Rejected('Redirect blocked')

def get(url, headers=None):
    target = urlsplit(url)
    permitted = ((target.netloc == 'paper-api.alpaca.markets' and target.path == '/v2/account') or
                 (target.netloc == 'data.alpaca.markets' and target.path == '/v1beta3/crypto/us/latest/quotes'))
    if target.scheme != 'https' or target.fragment or not permitted:
        raise Rejected('Only exact read-only paper account and crypto quote endpoints allowed')
    try:
        with build_opener(NoRedirect).open(Request(url, headers=headers or {}, method='GET'), timeout=10) as response:
            raw = response.read(1_000_001)
            if len(raw) > 1_000_000:
                raise Rejected('Response too large')
            return json.loads(raw, parse_float=str)
    except HTTPError as exc:
        raise Rejected('Broker/data request rejected, HTTP ' + str(exc.code)) from None
    except (URLError, TimeoutError, OSError, UnicodeError, json.JSONDecodeError):
        raise Rejected('Broker/data unavailable; no fallback prices used') from None

def canonical_paper_url(value):
    if value not in (PAPER,PAPER+'/',PAPER+'/v2',PAPER+'/v2/'):
        raise Rejected('Only the paper origin with an optional /v2 suffix is allowed')
    return PAPER

def credentials(settings):
    canonical_paper_url(settings.get('ALPACA_BASE_URL', PAPER))
    key, secret = settings.get('ALPACA_PAPER_KEY'), settings.get('ALPACA_PAPER_SECRET')
    if any(not isinstance(v,str) or not re.fullmatch(r'[A-Za-z0-9]{10,128}',v) or
           any(t in v.lower() for t in ('mock','replacement','placeholder','change_me','your-','your_')) for v in (key,secret)):
        raise Rejected('Paper credentials missing or placeholder; load through a private local file or environment')
    return {'APCA-API-KEY-ID':key,'APCA-API-SECRET-KEY':secret}

def account_amount(value):
    try:value=Decimal(str(value))
    except (InvalidOperation,ValueError):raise Rejected('Malformed account amount') from None
    if not value.is_finite():raise Rejected('Nonfinite account amount')
    return str(value)

def paper_account_check(settings=None):
    settings = os.environ if settings is None else settings
    a = get(PAPER + '/v2/account', credentials(settings))
    if not isinstance(a,dict) or not isinstance(a.get('status'),str) or not a['status'] or not isinstance(a.get('trading_blocked'),bool):
        raise Rejected('Incomplete account response; authentication not verified')
    for field in ('cash','equity','buying_power'):a[field]=account_amount(a.get(field))
    return {'status': a.get('status'), 'cash': a.get('cash'), 'equity': a.get('equity'),
            'buying_power':a['buying_power'],'currency':a.get('currency'),
            'trading_blocked': a.get('trading_blocked'), 'endpoint': PAPER,
            'credentials_authenticated': True, 'broker_order_submission': False}

def crypto_quotes(symbols=None, settings=None):
    symbols = ['BTC/USD','ETH/USD'] if symbols is None else symbols
    if not isinstance(symbols,list) or not symbols or len(set(symbols))!=len(symbols) or any(s not in QUOTE_SYMBOLS for s in symbols):
        raise Rejected('Only explicit BTC/USD,ETH/USD,SOL/USD quotes supported')
    settings=os.environ if settings is None else settings
    headers=credentials(settings) if settings.get('ALPACA_PAPER_KEY') or settings.get('ALPACA_PAPER_SECRET') else None
    result = get(DATA + '/v1beta3/crypto/us/latest/quotes?' + urlencode({'symbols':','.join(symbols)}),headers)
    if not isinstance(result,dict):raise Rejected('Malformed crypto quote response')
    q = result.get('quotes', {})
    if not isinstance(q,dict) or set(q) != set(symbols):
        raise Rejected('Incomplete crypto quote response')
    parsed={}
    try:
        for symbol,v in q.items():
            bid,ask=number(v['bp'],True),number(v['ap'],True)
            if bid>ask:raise Rejected('Crossed crypto quote')
            at=instant(v['t']);age=(datetime.now(timezone.utc)-at).total_seconds()
            if not -5<=age<=60:raise Rejected('Stale or future crypto quote')
            parsed[symbol]={'price':str(bid),'ask':str(ask),'at':at.isoformat(),'source':DATA}
    except (KeyError,TypeError):raise Rejected('Missing quote prices/timestamp') from None
    return parsed
