"""Monitor cen — codzienny odczyt cen z listy adresów, historia w SQLite, alert o spadku.

Użycie:
    python scraper.py produkty.csv            # odczyt + zapis + alerty
    python scraper.py produkty.csv --report   # tabela: produkt x sklep (ostatnie ceny)

produkty.csv (separator ;):
    produkt;sklep;url;selektor_css (opcjonalnie)

Cena jest szukana kolejno w: JSON-LD schema.org (offers.price), meta itemprop/og price,
a jeśli podano selektor CSS — w tym elemencie. Wymaga: pip install requests beautifulsoup4
Alert: Telegram (TG_TOKEN, TG_CHAT w zmiennych środowiskowych) albo wydruk w konsoli.
Uruchamianie co dzień: Harmonogram zadań Windows / cron / n8n (Execute Command).
"""
import csv, json, os, re, sqlite3, sys, time
from datetime import date

import requests
from bs4 import BeautifulSoup

DB = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ceny.sqlite')
DROP_PCT = float(os.environ.get('DROP_PCT', '5'))
UA = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
                    '(KHTML, like Gecko) Chrome/141.0 Safari/537.36',
      'Accept-Language': 'pl-PL,pl;q=0.9'}


def to_float(s):
    s = re.sub(r'[^\d,.]', '', str(s))
    if ',' in s and '.' in s:          # 1.299,00 -> 1299.00
        s = s.replace('.', '').replace(',', '.')
    else:
        s = s.replace(',', '.')
    try:
        return float(s)
    except ValueError:
        return None


def from_jsonld(soup):
    for tag in soup.find_all('script', type='application/ld+json'):
        try:
            data = json.loads(tag.string or '')
        except ValueError:
            continue
        stack = data if isinstance(data, list) else [data]
        while stack:
            d = stack.pop()
            if isinstance(d, list):
                stack += d
                continue
            if not isinstance(d, dict):
                continue
            stack += [v for v in d.values() if isinstance(v, (dict, list))]
            if d.get('@type') in ('Offer', 'AggregateOffer'):
                p = to_float(d.get('price') or d.get('lowPrice') or '')
                if p:
                    return p
    return None


def parse_price(html, selector=''):
    soup = BeautifulSoup(html, 'html.parser')
    p = from_jsonld(soup)
    if p:
        return p
    for attrs in ({'itemprop': 'price'}, {'property': 'product:price:amount'},
                  {'property': 'og:price:amount'}):
        m = soup.find(attrs=attrs)
        if m:
            p = to_float(m.get('content') or m.get_text())
            if p:
                return p
    if selector:
        el = soup.select_one(selector)
        if el:
            return to_float(el.get_text())
    return None


def db():
    con = sqlite3.connect(DB)
    con.execute('CREATE TABLE IF NOT EXISTS ceny (dzien TEXT, produkt TEXT, sklep TEXT, '
                'cena REAL, PRIMARY KEY (dzien, produkt, sklep))')
    return con


def alert(text):
    tok, chat = os.environ.get('TG_TOKEN'), os.environ.get('TG_CHAT')
    if tok and chat:
        requests.post(f'https://api.telegram.org/bot{tok}/sendMessage',
                      data={'chat_id': chat, 'text': text}, timeout=20)
    print('ALERT:', text)


def run(path):
    con, today = db(), date.today().isoformat()
    with open(path, encoding='utf-8-sig') as f:
        rows = list(csv.DictReader(f, delimiter=';'))
    for r in rows:
        try:
            html = requests.get(r['url'], headers=UA, timeout=30).text
            price = parse_price(html, r.get('selektor_css', ''))
        except requests.RequestException as e:
            print('BŁĄD', r['url'], e)
            continue
        if price is None:
            print('BRAK CENY', r['produkt'], r['sklep'], '- podaj selektor_css')
            continue
        prev = con.execute('SELECT cena FROM ceny WHERE produkt=? AND sklep=? AND dzien<? '
                           'ORDER BY dzien DESC LIMIT 1', (r['produkt'], r['sklep'], today)).fetchone()
        con.execute('INSERT OR REPLACE INTO ceny VALUES (?,?,?,?)', (today, r['produkt'], r['sklep'], price))
        print(f"{r['produkt']} | {r['sklep']} | {price:.2f} zł")
        if prev and (prev[0] - price) / prev[0] * 100 >= DROP_PCT:
            alert(f"Spadek ceny: {r['produkt']} w {r['sklep']}: {prev[0]:.2f} → {price:.2f} zł\n{r['url']}")
        time.sleep(2)  # grzecznie wobec sklepów
    con.commit()


def report():
    con = db()
    q = ('SELECT produkt, sklep, cena FROM ceny c WHERE dzien = (SELECT MAX(dzien) FROM ceny '
         'WHERE produkt=c.produkt AND sklep=c.sklep) ORDER BY produkt, cena')
    for produkt, sklep, cena in con.execute(q):
        print(f'{produkt:40} {sklep:20} {cena:10.2f} zł')


if __name__ == '__main__':
    if len(sys.argv) < 2:
        sys.exit(__doc__)
    report() if '--report' in sys.argv else run(sys.argv[1])
