#!/usr/bin/env python3
# Capa 0 (enlaces): comprueba status de los enlaces internos salientes hallados en el crawl.
# Marca rotos (>=400/error) y qué páginas los referencian. stdlib, coste 0.
import json, os, ssl, urllib.request, urllib.parse
from concurrent.futures import ThreadPoolExecutor, as_completed
DIR=os.path.dirname(os.path.abspath(__file__))
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
SITE='farmer.taild3aaf6.ts.net'
CAP=int(os.environ.get('CAP','9000'))

def norm(u):
    return u.split('#')[0].rstrip('/')

crawled={}   # url normalizada -> status (de report)
referers={}  # link -> set(paginas)
links=set()
with open(os.path.join(DIR,'report.jsonl'),encoding='utf-8') as f:
    for line in f:
        line=line.strip()
        if not line: continue
        r=json.loads(line)
        if r.get('final'): crawled[norm(r['final'])]=r.get('status')
        crawled.setdefault(norm(r['url']), r.get('status'))
        for l in (r.get('links') or []):
            if SITE in l:
                n=norm(l); links.add(n); referers.setdefault(n,set()).add(r['url'])

# solo comprobar las que NO conocemos ya por el crawl
todo=[l for l in links if l not in crawled]
capped = len(todo)>CAP
todo=todo[:CAP]
print(f"enlaces internos únicos: {len(links)} | ya conocidos: {len(links)-len(todo)-(len(todo) if False else 0)} | a comprobar: {len(todo)}"+(" (CAP)" if capped else ""))

def check(u):
    try:
        req=urllib.request.Request(u, method='HEAD', headers={'User-Agent':'fea-link-audit'})
        with urllib.request.urlopen(req,timeout=15,context=ctx) as r: return (u,r.status)
    except urllib.error.HTTPError as e:
        if e.code in (405,501):  # HEAD no permitido → GET
            try:
                req=urllib.request.Request(u, headers={'User-Agent':'fea-link-audit'})
                with urllib.request.urlopen(req,timeout=20,context=ctx) as r: return (u,r.status)
            except Exception as e2: return (u, getattr(e2,'code',-1))
        return (u,e.code)
    except Exception:
        # reintento único (evita falsos -1 por saturación)
        try:
            req=urllib.request.Request(u, headers={'User-Agent':'fea-link-audit'})
            with urllib.request.urlopen(req,timeout=25,context=ctx) as r: return (u,r.status)
        except Exception: return (u,-1)

res={}
with ThreadPoolExecutor(max_workers=8) as ex:
    futs=[ex.submit(check,u) for u in todo]
    for i,fu in enumerate(as_completed(futs)):
        u,s=fu.result(); res[u]=s
        if (i+1)%500==0: print(f"  {i+1}/{len(todo)}")

# combinar
allstatus=dict(crawled); allstatus.update(res)
broken=[]
for l in links:
    s=allstatus.get(l)
    if s is not None and (s==-1 or s>=400):
        broken.append({'link':l,'status':s,'referenced_by':sorted(referers.get(l,[]))[:8],'n_referers':len(referers.get(l,[]))})
broken.sort(key=lambda b:(-b['n_referers'], b['link']))
json.dump({'capped':capped,'checked':len(todo),'unique_links':len(links),'broken':broken},
          open(os.path.join(DIR,'broken_links.json'),'w',encoding='utf-8'),ensure_ascii=False,indent=1)
print(f"ENLACES ROTOS: {len(broken)} (de {len(links)} únicos)")
for b in broken[:15]: print(f"  [{b['status']}] {b['link']}  <- {b['n_referers']} págs")
