from __future__ import annotations
import json, re
from dataclasses import dataclass
from urllib.parse import urljoin
from bs4 import BeautifulSoup

@dataclass
class Offer:
    title: str | None
    price: float
    currency: str
    in_stock: bool | None
    image_url: str | None = None


def _price(v):
    if v is None: return None
    if isinstance(v, (int,float)): return float(v)
    s=str(v).strip().replace('\xa0',' ')
    s=re.sub(r'[^0-9,\.]','',s)
    if not s: return None
    if ',' in s and '.' in s:
        if s.rfind(',') > s.rfind('.'):
            s=s.replace('.','').replace(',','.')
        else:
            s=s.replace(',','')
    elif ',' in s:
        tail=s.split(',')[-1]
        s=s.replace('.','').replace(',','.' if len(tail)<=2 else '')
    elif s.count('.')>1:
        s=s.replace('.','')
    try:
        p=float(s)
        return p if 0.01 <= p <= 100_000_000 else None
    except ValueError: return None


def _stock(v):
    if v is None: return None
    s=str(v).lower()
    if 'outofstock' in s or 'out_of_stock' in s or 'tükendi' in s or 'stokta yok' in s: return False
    if 'instock' in s or 'in_stock' in s or 'stokta' in s: return True
    return None


def _image(v):
    if isinstance(v, str): return v.strip() or None
    if isinstance(v, list):
        for x in v:
            got=_image(x)
            if got: return got
    if isinstance(v, dict):
        for k in ('contentUrl','url','thumbnailUrl'):
            got=_image(v.get(k))
            if got: return got
    return None


def _walk_json(obj, page_title=None, inherited_image=None):
    out=[]
    if isinstance(obj,list):
        for x in obj: out += _walk_json(x,page_title,inherited_image)
    elif isinstance(obj,dict):
        typ=obj.get('@type')
        types={typ} if isinstance(typ,str) else set(typ or []) if isinstance(typ,list) else set()
        local_image=_image(obj.get('image')) or inherited_image
        if 'Product' in types:
            title=obj.get('name') or page_title
            offers=obj.get('offers')
            if offers: out += _walk_offer(offers,title,local_image)
        elif 'Offer' in types or 'AggregateOffer' in types:
            out += _walk_offer(obj,page_title,local_image)
        for k,v in obj.items():
            if k not in ('offers',): out += _walk_json(v,page_title,local_image)
    return out


def _walk_offer(obj,title,image_url=None):
    if isinstance(obj,list):
        out=[]
        for x in obj: out += _walk_offer(x,title,image_url)
        return out
    if not isinstance(obj,dict): return []
    vals=[]
    offer_image=_image(obj.get('image')) or image_url
    for key in ('price','lowPrice','highPrice'):
        p=_price(obj.get(key))
        if p is not None:
            vals.append(Offer(title, p, str(obj.get('priceCurrency') or 'TRY').upper(), _stock(obj.get('availability')), offer_image))
            break
    return vals


def extract(html:str, url:str) -> Offer:
    soup=BeautifulSoup(html,'lxml')
    title=(soup.title.get_text(' ',strip=True) if soup.title else None)
    candidates=[]
    for tag in soup.find_all('script', attrs={'type':'application/ld+json'}):
        raw=tag.string or tag.get_text()
        if not raw: continue
        try: candidates += _walk_json(json.loads(raw), title)
        except Exception: continue

    meta_image=None
    for attrs in ({'property':'og:image'}, {'property':'og:image:secure_url'}, {'name':'twitter:image'}, {'itemprop':'image'}):
        tag=soup.find(['meta','link'],attrs=attrs)
        if tag:
            meta_image=tag.get('content') or tag.get('href')
            if meta_image: break

    meta_price=None; currency='TRY'; stock=None
    price_selectors=[
        ('meta', {'property':'product:price:amount'}, 'content'),
        ('meta', {'property':'og:price:amount'}, 'content'),
        ('meta', {'itemprop':'price'}, 'content'),
    ]
    for tagname,attrs,field in price_selectors:
        tag=soup.find(tagname,attrs=attrs)
        if tag:
            meta_price=_price(tag.get(field));
            if meta_price is not None: break
    cur=soup.find('meta',attrs={'property':'product:price:currency'}) or soup.find('meta',attrs={'itemprop':'priceCurrency'})
    if cur and cur.get('content'): currency=cur.get('content').upper()
    av=soup.find('link',attrs={'itemprop':'availability'}) or soup.find('meta',attrs={'itemprop':'availability'})
    if av: stock=_stock(av.get('href') or av.get('content'))
    if meta_price is not None: candidates.append(Offer(title,meta_price,currency,stock,meta_image))

    if not candidates:
        patterns=[
            r'"price"\s*:\s*"?([0-9][0-9\.,]{1,15})',
            r'"currentPrice"\s*:\s*"?([0-9][0-9\.,]{1,15})',
            r'([0-9][0-9\.]{1,12},[0-9]{2})\s*(?:₺|TL|TRY)'
        ]
        for pat in patterns:
            m=re.search(pat,html,re.I)
            if m:
                p=_price(m.group(1))
                if p is not None:
                    candidates.append(Offer(title,p,'TRY',None,meta_image)); break

    if not candidates: raise ValueError('Sayfada güvenilir fiyat alanı bulunamadı')
    offer=candidates[0]
    if not offer.image_url and meta_image: offer.image_url=meta_image
    if offer.image_url: offer.image_url=urljoin(url, offer.image_url)
    return offer
