from __future__ import annotations
import hashlib, ipaddress, os, socket
from datetime import datetime, timezone
from pathlib import Path
from urllib.parse import urljoin, urlparse
from PIL import Image

IMAGE_DIR=Path(os.environ.get('FIYAT_RADAR_IMAGE_DIR','/var/lib/fiyat-radar/images'))
UA=os.environ.get('FIYAT_RADAR_UA','Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36')
TIMEOUT=int(os.environ.get('FIYAT_RADAR_IMAGE_TIMEOUT','15'))
MAX_IMAGE_BYTES=int(os.environ.get('FIYAT_RADAR_MAX_IMAGE_BYTES',str(8*1024*1024)))
MIN_IMAGE_WIDTH=int(os.environ.get('FIYAT_RADAR_MIN_IMAGE_WIDTH','180'))
MIN_IMAGE_HEIGHT=int(os.environ.get('FIYAT_RADAR_MIN_IMAGE_HEIGHT','180'))


def now(): return datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')


def _public_host(host):
    try: infos=socket.getaddrinfo(host,None,type=socket.SOCK_STREAM)
    except socket.gaierror:return False
    for raw in {x[4][0] for x in infos}:
        try:
            if not ipaddress.ip_address(raw).is_global:return False
        except ValueError:return False
    return bool(infos)


def safe_image_url(url):
    p=urlparse(url or '')
    if p.scheme not in ('http','https') or not p.hostname or not _public_host(p.hostname):
        raise ValueError('Görsel URL güvenli değil')
    return url


def safe_image_get(session,url,source_page_url=None):
    current=url
    for _ in range(5):
        safe_image_url(current)
        headers={'User-Agent':UA,'Accept':'image/avif,image/webp,image/apng,image/*,*/*;q=0.8'}
        if source_page_url: headers['Referer']=source_page_url
        r=session.get(current,timeout=TIMEOUT,headers=headers,stream=True,allow_redirects=False)
        if r.status_code in (301,302,303,307,308):
            loc=r.headers.get('location');r.close()
            if not loc:raise ValueError('Görsel yönlendirmesinde Location yok')
            current=urljoin(current,loc);continue
        r.raise_for_status();return r,current
    raise ValueError('Görsel çok fazla yönlendirme yaptı')


def _image_ext(ct):
    return {'image/jpeg':'jpg','image/jpg':'jpg','image/png':'png','image/webp':'webp','image/gif':'gif','image/avif':'avif'}.get((ct or '').split(';',1)[0].strip().lower(),'')


def cache_product_image(c,session,product,image_url,*,source_page_url=None,source_kind='page',confidence=.80,force=False):
    """Gerçek görseli yerel diske indir, boyut/format kontrolü yap ve ürüne bağla."""
    product_id=int(product.get('product_id') or product.get('id'))
    old_path=product.get('image_path')
    old_url=product.get('image_source_url')
    if old_path and int(product.get('image_locked') or 0)==1 and not force:
        return old_path
    if old_path and old_url==image_url and not force:
        return old_path
    r,final_url=safe_image_get(session,image_url,source_page_url)
    tmp=None
    try:
        ext=_image_ext(r.headers.get('content-type',''))
        if not ext:raise ValueError('Görsel Content-Type desteklenmiyor')
        if r.headers.get('content-length') and int(r.headers['content-length'])>MAX_IMAGE_BYTES:
            raise ValueError('Görsel boyut sınırını aşıyor')
        IMAGE_DIR.mkdir(parents=True,exist_ok=True)
        digest=hashlib.sha256((final_url+'|'+str(product_id)).encode()).hexdigest()[:16]
        filename=f'product-{product_id}-{digest}.{ext}'
        tmp=IMAGE_DIR/(filename+'.tmp');total=0
        with open(tmp,'wb') as f:
            for chunk in r.iter_content(65536):
                if not chunk:continue
                total+=len(chunk)
                if total>MAX_IMAGE_BYTES:raise ValueError('Görsel boyut sınırını aşıyor')
                f.write(chunk)
        if total<1024:raise ValueError('Görsel çok küçük/geçersiz')
        width=height=0
        try:
            with Image.open(tmp) as im:
                width,height=im.size
                im.verify()
        except Exception as e:
            # AVIF/WebP gibi formatlarda codec eksikse gerçek image/* içeriğini tamamen kaybetme.
            # Ancak küçük/tracking görselini yine kabul etme.
            if ext not in {'avif','webp'} or total<12_000:
                raise ValueError(f'Görsel doğrulanamadı: {e}') from e
        if width and height and (width<MIN_IMAGE_WIDTH or height<MIN_IMAGE_HEIGHT):
            raise ValueError(f'Görsel çözünürlüğü çok düşük: {width}x{height}')
        dest=IMAGE_DIR/filename;os.replace(tmp,dest);tmp=None
        if old_path and old_path!=filename:
            # Aynı fiziksel görsel family+renk kardeşleri tarafından ortak kullanılabilir.
            # Başka bir ürün referans veriyorsa dosyayı asla silme.
            try:
                refs=c.execute("SELECT COUNT(*) FROM products WHERE id<>? AND image_path=?",(product_id,old_path)).fetchone()[0]
                if int(refs or 0)==0:(IMAGE_DIR/Path(old_path).name).unlink(missing_ok=True)
            except Exception:pass
        host=(urlparse(source_page_url or final_url).hostname or '').lower()
        rank=100 if 'hepsiburada.com' in host else (90 if source_kind.startswith('consensus:') else 60)
        locked=1 if source_kind.startswith('consensus:') or 'hepsiburada.com' in host else 0
        c.execute('''UPDATE products SET image_path=?,image_source_url=?,image_source_page_url=?,image_source_kind=?,image_match_confidence=?,image_status='ready',image_locked=MAX(COALESCE(image_locked,0),?),image_verified_at=CASE WHEN ?=1 THEN ? ELSE image_verified_at END,image_provider_rank=MAX(COALESCE(image_provider_rank,0),?),image_checked_at=?,image_updated_at=? WHERE id=?''',
                  (filename,final_url,source_page_url,source_kind,float(confidence),locked,locked,now(),rank,now(),now(),product_id))
        return filename
    finally:
        r.close()
        if tmp is not None:
            try:tmp.unlink(missing_ok=True)
            except OSError:pass
