from __future__ import annotations
import re, unicodedata
from condition import assess_condition, validate_condition, normalize_condition


def norm(s):
    s=(s or '').casefold().replace('ı','i').replace('İ','i')
    s=''.join(c for c in unicodedata.normalize('NFKD',s) if not unicodedata.combining(c))
    return re.sub(r'[^a-z0-9]+',' ',s).strip()


def tokens(s):
    return [x for x in norm(s).split() if len(x)>=2]


def _get(product,key,default=None):
    try:
        v=product.get(key,default)
    except AttributeError:
        try: v=product[key]
        except Exception: v=default
    return v



# Bir model ailesinde farklı fiziksel ürünü ifade eden güçlü kardeş-model niteleyicileri.
# Örn. iPhone 16 != iPhone 16 Plus != iPhone 16 Pro != iPhone 16 Pro Max.
# Ağ nesli (5G/LTE) burada bilinçli olarak yoktur; bazı üreticiler başlıkta pazarlama terimi olarak
# kullanır. Ağ nesli ayrımı gerekiyorsa model/variant alanında ayrıca doğrulanır.
MODEL_SIBLING_QUALIFIERS={
    'plus','pro','max','ultra','mini','air','fe','fold','flip','lite','se','edge','neo'
}

def model_qualifiers(text):
    return {t for t in tokens(text) if t in MODEL_SIBLING_QUALIFIERS}

def _candidate_model_qualifiers(product,text):
    """Yalnız model çekirdeğinin içindeki/hemen sonrasındaki niteleyicileri oku.

    Böylece mağaza adı "Pro Teknoloji" gibi modelden uzaktaki kelimeler yanlışlıkla
    iPhone 16 Pro sanılmaz. Galaxy Z Flip 7 gibi niteleyicinin çekirdeğin ortasında
    bulunduğu modeller de desteklenir.
    """
    expected=tokens(expected_model_text(product))
    core=[t for t in expected if t not in MODEL_SIBLING_QUALIFIERS]
    cand=tokens(text)
    if not core or not cand:
        return set()
    for start,t in enumerate(cand):
        if t!=core[0]:
            continue
        i=start+1; got=set(); ok=True
        for wanted in core[1:]:
            while i<len(cand) and cand[i] in MODEL_SIBLING_QUALIFIERS:
                got.add(cand[i]); i+=1
            if i>=len(cand) or cand[i]!=wanted:
                ok=False; break
            i+=1
        if not ok:
            continue
        while i<len(cand) and cand[i] in MODEL_SIBLING_QUALIFIERS:
            got.add(cand[i]); i+=1
        return got
    return set()

def expected_model_text(product):
    model=str(_get(product,'model','') or '').strip()
    variant=str(_get(product,'variant','') or '').strip()
    if model:
        return (model+' '+variant).strip()
    return (str(_get(product,'name','') or _get(product,'product_name','') or '')+' '+variant).strip()

def model_identity_conflict(product,text):
    """Kardeş model karışmasını yakala; None uyumlu demektir.

    Bu kontrol kapasite/renkten bağımsızdır ve aday URL daha açılmadan uygulanabilir.
    Özellikle 'iPhone 16' ürününe 'iPhone 16 Plus' linki eklenmesini engeller.
    """
    expected=expected_model_text(product)
    exp_q=model_qualifiers(expected)
    got_q=_candidate_model_qualifiers(product,text)
    extra=sorted(got_q-exp_q)
    missing=sorted(exp_q-got_q)
    if extra:
        return 'Kardeş model niteleyicisi uyuşmuyor: beklenmeyen '+', '.join(extra)
    if missing:
        return 'Model niteleyicisi eksik: '+', '.join(missing)
    return None

def model_compatible(product,text):
    return model_identity_conflict(product,text) is None

def validate_model_identity(product,text):
    reason=model_identity_conflict(product,text)
    if reason:
        raise ValueError(reason)
    return True

PHONE_BRANDS={'apple','samsung','xiaomi','redmi','poco','huawei','honor','oppo','realme','oneplus','sony','lg','motorola','vivo','asus','nothing','google','zte','tecno','infinix','nokia'}
PHONE_STORAGE_GB={16,32,64,128,256,512,1024,2048}

def _phone_product(product):
    if product is None:return False
    b=norm(str(_get(product,'brand','') or ''));cat=norm(str(_get(product,'category','') or ''));txt=' '+norm(expected_model_text(product))+' '
    return b in PHONE_BRANDS or any(x in cat for x in ('telefon','smartphone','cep telefonu','akilli telefon')) or ' iphone ' in txt or ' galaxy ' in txt

def capacity_values(text,product=None):
    """Başlıktaki *depolama* kapasitelerini döndürür; RAM değerlerini dışlar.

    Özellikle ``6 GB RAM 128 GB`` gibi başlıklarda 6 GB artık depolama sayılmaz.
    Telefonlarda yalnız gerçekçi storage boyutları kabul edilir.
    """
    n=norm(text);out=set();phone=_phone_product(product)
    for m in re.finditer(r'(?<!\d)(\d{1,4})\s*(gb|gbyte|gigabyte)(?![a-z])',n,re.I):
        v=int(m.group(1));suffix=n[m.end():m.end()+14]
        if re.match(r'\s*(ram|bellek|memory|lpddr|ddr\d*)\b',suffix):continue
        if phone and v not in PHONE_STORAGE_GB:continue
        out.add(v)
    for m in re.finditer(r'(?<!\d)(\d{1,2}(?:[\.,]\d+)?)\s*(tb|tbyte|terabyte)(?![a-z])',n,re.I):
        try:
            tb=float(m.group(1).replace(',','.'));v=int(round(tb*1024))
            if phone and v not in PHONE_STORAGE_GB:continue
            out.add(v)
        except ValueError:pass
    return out


COLOR_ALIASES={
    'siyah':['siyah','black'],
    'beyaz':['beyaz','white'],
    'mavi':['mavi','blue'],
    'lacivert':['lacivert','navy','navy blue'],
    'yesil':['yesil','green'],
    'kirmizi':['kirmizi','red'],
    'mor':['mor','purple','violet'],
    'pembe':['pembe','pink'],
    'gri':['gri','gray','grey'],
    'gumus':['gumus','silver'],
    'altin':['altin','gold'],
    'sari':['sari','yellow'],
    'turuncu':['turuncu','orange'],
    'kahverengi':['kahverengi','brown'],
    'bej':['bej','beige'],
    'dogal titanyum':['dogal titanyum','natural titanium'],
    'siyah titanyum':['siyah titanyum','black titanium'],
    'beyaz titanyum':['beyaz titanyum','white titanium'],
    'col titanyum':['col titanyum','desert titanium'],
    'naturel titanyum':['naturel titanyum','natural titanium','dogal titanyum'],
    'deniz mavisi':['deniz mavisi','teal'],
    'laciverttas':['laciverttas','ultramarine'],
    'gece yarisi':['gece yarisi','midnight'],
    'yildiz isigi':['yildiz isigi','starlight'],
    'product red':['product red','red'],
    'sis mavisi':['sis mavisi','mist blue'],
    'ada cayi':['ada cayi','sage'],
    'lavanta':['lavanta','lavender'],
    'kozmik turuncu':['kozmik turuncu','cosmic orange'],
    'abis':['abis','deep blue'],
}


def color_matches(color,text):
    color_n=norm(color)
    if not color_n: return True
    text_n=' '+norm(text)+' '
    aliases=COLOR_ALIASES.get(color_n,[color_n])
    for alias in aliases:
        phrase=' '+norm(alias)+' '
        if phrase in text_n:
            return True
    # Üreticinin özel renk adlarında tüm kelimelerin bulunması da kabul edilir.
    ct=tokens(color_n)
    return bool(ct) and all((' '+t+' ') in text_n for t in ct)


def product_query(product, include_price_word=True):
    parts=[]
    for key in ('brand','model'):
        v=str(_get(product,key,'') or '').strip()
        if v and norm(v) not in norm(' '.join(parts)): parts.append(v)
    name=str(_get(product,'name','') or _get(product,'product_name','') or '').strip()
    if name:
        for t in name.split():
            if norm(t) not in norm(' '.join(parts)): parts.append(t)
    cap=_get(product,'capacity_gb')
    if cap:
        try: parts.append(f'{int(cap)} GB')
        except Exception: pass
    color=str(_get(product,'color','') or '').strip()
    if color: parts.append(color)
    variant=str(_get(product,'variant','') or '').strip()
    if variant: parts.append(variant)
    ean=str(_get(product,'ean','') or '').strip()
    if ean: parts.append(ean)
    condition=normalize_condition(_get(product,'condition_type','new'))
    if condition=='refurbished': parts.append('yenilenmiş')
    elif condition=='open_box': parts.append('açık kutu')
    elif condition=='used': parts.append('ikinci el')
    if include_price_word: parts.append('fiyat')
    return ' '.join(parts).strip()


def confidence(product,title,html='',url='',condition_text=''):
    # Kardeş model ve açık ürün-durumu çatışması asla yüksek güven alamaz.
    if not model_compatible(product,title):
        return 0.0
    ca=assess_condition(product,title,url,condition_text,require_known=False)
    if ca.status in ('mismatch','conflict'):
        return 0.0
    title_n=norm(title)
    important=[]
    for field in ('brand','model','variant'):
        important += tokens(_get(product,field,''))
    important += [t for t in tokens(_get(product,'name','') or _get(product,'product_name','')) if t not in {'urun','telefon','akilli','cep','gb','tb'}]
    important=list(dict.fromkeys(important))
    score=0.48 if not important else sum(1 for t in important if t in title_n)/len(important)*0.68

    cap=_get(product,'capacity_gb')
    if cap:
        try:
            expected=int(cap)
            # Telefonlarda 4/6/8/12 GB gibi RAM boyutları depolama varyantı olamaz.
            # Geçersiz bir beklenen kapasiteye hiçbir başlık yüksek güven alamaz.
            if _phone_product(product) and expected not in PHONE_STORAGE_GB:
                return 0.0
            found=capacity_values(title,product)
            # Başlık başka bir gerçek depolama kapasitesini açıkça söylüyorsa bu kaynak
            # aynı varyant değildir; final validator'a bırakmadan keşif aşamasında kes.
            if found and expected not in found:
                return 0.0
            if expected in found:
                score += 0.16
            elif _phone_product(product):
                # Kapasitesi belli bir telefon varyantında başlık depolama boyutunu hiç
                # kanıtlamıyorsa otomatik kaynak kabulüne yüksek güven verme.
                return 0.0
        except Exception:
            return 0.0
    color=_get(product,'color','')
    if color and color_matches(color,title): score += 0.10

    ean=str(_get(product,'ean','') or '').strip()
    if ean and ean in (html or ''): score += 0.25
    elif ean and ean in title_n: score += 0.25
    return min(1.0,score)


def validate_product_match(product,title,html='',url='',condition_text='',enforce_condition=True):
    # Önce kesin model kimliği: iPhone 16 ile iPhone 16 Plus gibi kardeşler ayrılır.
    validate_model_identity(product,title)
    if enforce_condition:
        validate_condition(product,title,url,condition_text,require_known=True)
    title_n=norm(title)
    bt=tokens(_get(product,'brand',''))
    if bt and not any(t in title_n for t in bt):
        raise ValueError('Ürün başlığında marka eşleşmedi')

    for t in tokens(_get(product,'model','')):
        if any(ch.isdigit() for ch in t) and t not in title_n:
            raise ValueError(f'Model belirteci eşleşmedi: {t}')

    for t in tokens(_get(product,'variant','')):
        if any(ch.isdigit() for ch in t) and t not in title_n:
            raise ValueError(f'Varyant belirteci eşleşmedi: {t}')

    name_tokens=[t for t in tokens(_get(product,'name','') or _get(product,'product_name','')) if t not in {'urun','telefon','akilli','cep','gb','tb'}]
    if name_tokens:
        numeric=[t for t in name_tokens if any(ch.isdigit() for ch in t)]
        for t in numeric:
            if t not in title_n: raise ValueError(f'Ürün adı belirteci eşleşmedi: {t}')
        matched=sum(1 for t in name_tokens if t in title_n)
        if matched < max(1,(len(name_tokens)+1)//2):
            raise ValueError('Ürün adı eşleşmesi yetersiz')

    cap=_get(product,'capacity_gb')
    if cap:
        expected=int(cap)
        found=capacity_values(title,product)
        if expected not in found:
            if found:
                raise ValueError(f'Kapasite eşleşmedi: beklenen {expected} GB, bulunan {sorted(found)} GB')
            raise ValueError(f'Ürün başlığında {expected} GB kapasite bulunamadı')

    color=str(_get(product,'color','') or '').strip()
    if color and not color_matches(color,title):
        raise ValueError(f'Renk eşleşmedi: {color}')


def product_family_query(product):
    """Kapasite/renk eklemeden ürün ailesini aramak için kısa sorgu.

    Aynı modelin 10 varyantı varsa mağazaya 10 ayrı arama göndermek yerine
    aileyi bir kez arayıp sonuçları varyantlara dağıtmak için kullanılır.
    """
    parts=[]
    for key in ('brand','model'):
        v=str(_get(product,key,'') or '').strip()
        if v and norm(v) not in norm(' '.join(parts)):
            parts.append(v)
    name=str(_get(product,'name','') or _get(product,'product_name','') or '').strip()
    if name:
        for t in name.split():
            nt=norm(t)
            if nt and nt not in norm(' '.join(parts)):
                parts.append(t)
    variant=str(_get(product,'variant','') or '').strip()
    if variant:
        for t in variant.split():
            nt=norm(t)
            if nt and nt not in norm(' '.join(parts)):
                parts.append(t)
    condition=normalize_condition(_get(product,'condition_type','new'))
    if condition=='refurbished': parts.append('yenilenmiş')
    elif condition=='open_box': parts.append('açık kutu')
    elif condition=='used': parts.append('ikinci el')
    return ' '.join(parts).strip()


def product_family_key(product):
    condition=normalize_condition(_get(product,'condition_type','new'))
    return norm(product_family_query(product))+'|'+condition
