from urllib.parse import urlsplit
SAFE_METHODS={'GET','HEAD','OPTIONS'}

def normalized_origin(url):
    p=urlsplit(url); scheme=p.scheme.lower(); host=(p.hostname or '').lower(); port=p.port
    if port is None: port=443 if scheme=='https' else 80 if scheme=='http' else None
    return f"{scheme}://{host}" + (f":{port}" if port not in (None,443 if scheme=='https' else 80) else '')

def relation(a,b,site_map):
    oa,ob=normalized_origin(a),normalized_origin(b)
    if oa==ob: return 'same-origin'
    if site_map.get(oa) and site_map.get(oa)==site_map.get(ob): return 'same-site-different-origin'
    return 'cross-site'

def evaluate_csrf(req,policy):
    method=req.get('method','GET').upper()
    if not req.get('state_change'):
        return 'CSRF_NOT_REQUIRED_FOR_FIXTURE_FLOW'
    if method in SAFE_METHODS:
        return 'DENY_STATE_CHANGE_SAFE_METHOD'
    if not req.get('business_authorized'):
        return 'DENY_AUTHORIZATION'
    if req.get('caller_origin') not in policy['trusted_state_origins']:
        return 'DENY_ORIGIN'
    if req.get('ambient_credentials') and policy.get('csrf_required_for_ambient_state_change',False):
        if req.get('csrf_signal') != policy.get('expected_csrf_signal'):
            return 'DENY_MISSING_CSRF'
    return 'ALLOW_STATE_CHANGE'

def evaluate_cors(req,policy):
    route=policy.get('cors',{}).get(req.get('url'))
    if route is None: return {'status':'CORS_NOT_CONFIGURED','shared':False}
    origin=req.get('caller_origin'); allowed=route.get('allowed_origins',[]); credentials=bool(req.get('ambient_credentials'))
    method=req.get('method','GET').upper(); allowed_methods={str(x).upper() for x in route.get('allowed_methods',[])}
    if allowed_methods and method not in allowed_methods:
        return {'status':'CORS_METHOD_NOT_SHARED','shared':False}
    if origin in allowed:
        if credentials and not route.get('allow_credentials',False): return {'status':'CORS_NOT_SHARED','shared':False}
        return {'status':'CORS_SHARED','shared':True}
    if '*' in allowed and not credentials:
        return {'status':'CORS_SHARED','shared':True}
    return {'status':'CORS_NOT_SHARED','shared':False}

def _source_allowed(source,page_origin,allowed):
    if "'self'" in allowed and normalized_origin(source)==normalized_origin(page_origin): return True
    return normalized_origin(source) in {normalized_origin(x) for x in allowed if not x.startswith("'")}

def evaluate_csp(resource,policy):
    csp=policy['csp']
    if normalized_origin(resource['page_origin']) != normalized_origin(csp['page_origin']):
        return {'status':'CSP_POLICY_NOT_APPLICABLE','blocked':False,'reported':False}
    directives=csp['directives']; typ=resource['resource_type']
    directive=directives.get(f'{typ}-src',directives.get('default-src',[]))
    allowed=_source_allowed(resource['source_origin'],resource['page_origin'],directive)
    mode=resource.get('mode','enforce')
    if allowed: return {'status':'CSP_ALLOWED','blocked':False,'reported':False}
    if mode=='report-only': return {'status':'CSP_REPORTED_NOT_BLOCKED','blocked':False,'reported':True}
    return {'status':'CSP_BLOCKED','blocked':True,'reported':True}

def evaluate_flow(req,policy,site_map):
    return {
      'flow':req['id'],
      'origin_site_relation':relation(req['caller_origin'],req['url'],site_map),
      'csrf':evaluate_csrf(req,policy),
      'cors':evaluate_cors(req,policy),
      'business_authorized':bool(req.get('business_authorized')),
      'state_change':bool(req.get('state_change')),
      'ambient_credentials':bool(req.get('ambient_credentials'))
    }
