#!/usr/bin/env python3
"""
Lotto 6aus49 Data Updater für lotto.de

Web-Scraper für https://www.lotto.de/lotto-6aus49/lottozahlen

Features:
- Automatisches Scraping aller historischen Ziehungen
- Duplikate-Vermeidung
- Automatisches Backup vor Update
- Validierung der Daten
- Fortschrittsanzeige
"""

import pandas as pd
import requests
from bs4 import BeautifulSoup
from datetime import datetime
import shutil
import os
from typing import List, Dict, Optional
import time


class LottoWebUpdater:
    """Updater für lotto.de Web-Scraping"""

    def __init__(self, data_file: str):
        self.data_file = data_file
        self.url = "https://www.lotto.de/lotto-6aus49/lottozahlen"
        self.backup_file = None
        self.df_existing = None

        print("🔄 LOTTO 6AUS49 WEB UPDATER")
        print("=" * 70)
        print(f"📁 Datei: {os.path.basename(data_file)}")
        print(f"🌐 Quelle: {self.url}")
        print("=" * 70)

    def load_existing_data(self) -> bool:
        """Lädt existierende Daten."""
        print("\n📂 Lade existierende Daten...")

        try:
            if not os.path.exists(self.data_file):
                print(f"   ⚠️  Datei existiert nicht - erstelle neue")
                self.df_existing = pd.DataFrame(
                    columns=['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'SZ']
                )
                return True

            self.df_existing = pd.read_csv(self.data_file, sep=';')

            # Konvertiere Datum
            if 'datum' in self.df_existing.columns:
                self.df_existing['datum'] = pd.to_datetime(
                    self.df_existing['datum'],
                    format='%Y-%m-%d',
                    errors='coerce'
                )

            print(f"   ✅ {len(self.df_existing)} Ziehungen geladen")

            if len(self.df_existing) > 0:
                latest = self.df_existing['datum'].max()
                oldest = self.df_existing['datum'].min()
                print(f"   📅 Zeitraum: {oldest.strftime('%Y-%m-%d')} bis {latest.strftime('%Y-%m-%d')}")

            return True

        except Exception as e:
            print(f"   ❌ Fehler: {e}")
            return False

    def create_backup(self) -> bool:
        """Erstellt Backup."""
        if not os.path.exists(self.data_file):
            return True

        try:
            print("\n💾 Erstelle Backup...")

            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            parent_dir = os.path.dirname(self.data_file)
            lotto_dir = os.path.join(parent_dir, "Lotto")
            backup_dir = os.path.join(lotto_dir, "data", "backups")
            os.makedirs(backup_dir, exist_ok=True)

            filename = os.path.basename(self.data_file)
            self.backup_file = os.path.join(backup_dir, f"{filename}.backup_{timestamp}")

            shutil.copy2(self.data_file, self.backup_file)
            print(f"   ✅ Backup: {os.path.basename(self.backup_file)}")

            return True

        except Exception as e:
            print(f"   ⚠️  Backup-Fehler: {e}")
            return False

    def fetch_draws_from_web(self, max_pages: int = 5) -> List[Dict]:
        """
        Scrapt Ziehungen von lotto.de

        Args:
            max_pages: Maximale Anzahl Seiten (jede Seite ~20 Ziehungen)

        Returns:
            Liste von Ziehungen
        """
        print(f"\n🌐 Lade Daten von {self.url}...")
        print(f"   📄 Lade bis zu {max_pages} Seiten...")

        all_draws = []

        try:
            headers = {
                'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
            }

            for page in range(1, max_pages + 1):
                url = f"{self.url}?page={page}" if page > 1 else self.url

                print(f"\n   📄 Seite {page}/{max_pages}...")

                response = requests.get(url, headers=headers, timeout=15)
                response.raise_for_status()

                soup = BeautifulSoup(response.content, 'html.parser')

                # Finde Ziehungen auf dieser Seite
                page_draws = self._extract_draws_from_page(soup, page)

                if not page_draws:
                    print(f"      ⚠️  Keine Ziehungen gefunden - Abbruch")
                    break

                all_draws.extend(page_draws)
                print(f"      ✅ {len(page_draws)} Ziehungen extrahiert")

                # Rate limiting
                if page < max_pages:
                    time.sleep(1)

            print(f"\n   ✅ Gesamt: {len(all_draws)} Ziehungen von {page} Seite(n)")
            return all_draws

        except requests.RequestException as e:
            print(f"   ❌ Netzwerkfehler: {e}")
            return all_draws  # Return was bisher geladen wurde
        except Exception as e:
            print(f"   ❌ Fehler: {e}")
            return all_draws

    def _extract_draws_from_page(self, soup: BeautifulSoup, page_num: int) -> List[Dict]:
        """
        Extrahiert Ziehungen von einer Seite.

        Args:
            soup: BeautifulSoup Objekt
            page_num: Seitennummer

        Returns:
            Liste von Ziehungen
        """
        draws = []

        try:
            # Verschiedene Selektoren versuchen
            # 1. Versuche mit data-attribute
            result_divs = soup.find_all('div', class_=lambda x: x and 'result' in x.lower())

            if not result_divs:
                # 2. Versuche table
                result_divs = soup.find_all('tr', class_=lambda x: x and ('draw' in x.lower() or 'result' in x.lower()))

            if not result_divs:
                # 3. Versuche generische Container
                result_divs = soup.find_all('div', class_=lambda x: x and 'drawing' in x.lower())

            print(f"      🔍 {len(result_divs)} potentielle Ergebnis-Container gefunden")

            for i, elem in enumerate(result_divs):
                try:
                    # Extrahiere Datum
                    date_elem = elem.find('time') or elem.find(class_=lambda x: x and 'date' in x.lower())
                    if not date_elem:
                        # Versuche direkten Text
                        date_text = self._extract_date_text(elem.get_text())
                    else:
                        date_text = date_elem.get_text().strip()

                    date_obj = self._parse_date(date_text)

                    # Extrahiere Zahlen - verschiedene Ansätze
                    numbers = self._extract_numbers(elem)

                    if len(numbers) < 7:  # Mindestens 6 Hauptzahlen + 1 Superzahl
                        continue

                    main_numbers = sorted(numbers[:6])
                    superzahl = numbers[6]

                    draw = {
                        'datum': date_obj,
                        'Z1': main_numbers[0],
                        'Z2': main_numbers[1],
                        'Z3': main_numbers[2],
                        'Z4': main_numbers[3],
                        'Z5': main_numbers[4],
                        'Z6': main_numbers[5],
                        'SZ': superzahl
                    }

                    if self._validate_draw(draw):
                        draws.append(draw)
                    else:
                        print(f"         ⚠️  Element {i+1}: Validierung fehlgeschlagen")

                except Exception as e:
                    continue

        except Exception as e:
            print(f"      ⚠️  Fehler beim Extrahieren: {e}")

        return draws

    def _extract_date_text(self, text: str) -> str:
        """Extrahiert Datum aus Text."""
        import re

        # Suche nach Datum im Format DD.MM.YYYY oder YYYY-MM-DD
        patterns = [
            r'(\d{2}\.\d{2}\.\d{4})',
            r'(\d{4}-\d{2}-\d{2})',
            r'(\d{1,2}\.\s*\w+\s*\d{4})'  # z.B. "22. Januar 2025"
        ]

        for pattern in patterns:
            match = re.search(pattern, text)
            if match:
                return match.group(1)

        return text.strip()

    def _extract_numbers(self, elem) -> List[int]:
        """Extrahiert Zahlen aus Element."""
        numbers = []

        # 1. Versuche mit span/div mit Klassen wie 'ball', 'number', etc.
        number_elems = elem.find_all(class_=lambda x: x and any(c in x.lower() for c in ['ball', 'number', 'zahl']))

        if number_elems:
            for num_elem in number_elems:
                try:
                    text = num_elem.get_text().strip()
                    # Extrahiere nur Zahlen
                    import re
                    nums = re.findall(r'\d+', text)
                    if nums:
                        numbers.append(int(nums[0]))
                except:
                    continue

        # 2. Fallback: Alle Zahlen aus Text extrahieren
        if len(numbers) < 7:
            import re
            text = elem.get_text()
            # Finde alle Zahlen
            all_nums = re.findall(r'\b(\d+)\b', text)

            # Filtere plausible Lotto-Zahlen (1-49 für Hauptzahlen, 0-9 für Superzahl)
            plausible = []
            for n in all_nums:
                num = int(n)
                if 1 <= num <= 49:
                    plausible.append(num)
                elif 0 <= num <= 9 and len(plausible) >= 6:
                    # Könnte Superzahl sein
                    plausible.append(num)

            if len(plausible) >= 7:
                numbers = plausible[:7]

        return numbers

    def _parse_date(self, date_str: str) -> datetime:
        """Parst Datum."""
        import re

        # Clean up
        date_str = date_str.strip()

        # Format 1: DD.MM.YYYY
        try:
            return datetime.strptime(date_str, '%d.%m.%Y')
        except ValueError:
            pass

        # Format 2: YYYY-MM-DD
        try:
            return datetime.strptime(date_str, '%Y-%m-%d')
        except ValueError:
            pass

        # Format 3: DD. MONAT YYYY (z.B. "22. Januar 2025")
        months_de = {
            'januar': 1, 'februar': 2, 'märz': 3, 'april': 4,
            'mai': 5, 'juni': 6, 'juli': 7, 'august': 8,
            'september': 9, 'oktober': 10, 'november': 11, 'dezember': 12
        }

        match = re.search(r'(\d{1,2})\.\s*(\w+)\s*(\d{4})', date_str.lower())
        if match:
            day = int(match.group(1))
            month_str = match.group(2)
            year = int(match.group(3))

            if month_str in months_de:
                return datetime(year, months_de[month_str], day)

        # Fallback: Aktuelles Datum
        print(f"         ⚠️  Konnte Datum nicht parsen: {date_str}")
        return datetime.now()

    def _validate_draw(self, draw: Dict) -> bool:
        """Validiert eine Ziehung."""
        try:
            required_fields = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'SZ']
            if not all(field in draw for field in required_fields):
                return False

            # Hauptzahlen (1-49)
            main_numbers = [draw[f'Z{i}'] for i in range(1, 7)]
            if not all(isinstance(n, int) and 1 <= n <= 49 for n in main_numbers):
                return False

            if len(set(main_numbers)) != 6:
                return False

            # Superzahl (0-9)
            if not isinstance(draw['SZ'], int) or not 0 <= draw['SZ'] <= 9:
                return False

            if not isinstance(draw['datum'], datetime):
                return False

            return True

        except Exception:
            return False

    def merge_with_existing(self, new_draws: List[Dict]) -> pd.DataFrame:
        """Merged neue Ziehungen mit existierenden Daten."""
        print(f"\n🔀 Merge mit existierenden Daten...")

        if not new_draws:
            print("   ⚠️  Keine neuen Ziehungen zum Mergen")
            return self.df_existing

        df_new = pd.DataFrame(new_draws)

        if self.df_existing is None or len(self.df_existing) == 0:
            df_combined = df_new
            print(f"   ✅ Neue Datei erstellt mit {len(df_combined)} Ziehungen")
        else:
            df_combined = pd.concat([self.df_existing, df_new], ignore_index=True)

            before_dedup = len(df_combined)
            df_combined = df_combined.drop_duplicates(subset=['datum'], keep='first')
            after_dedup = len(df_combined)

            duplicates = before_dedup - after_dedup
            if duplicates > 0:
                print(f"   🗑️  {duplicates} Duplikat(e) entfernt")

        df_combined = df_combined.sort_values('datum', ascending=True)
        df_combined = df_combined.reset_index(drop=True)

        new_entries = len(df_combined) - (len(self.df_existing) if self.df_existing is not None else 0)

        print(f"   ✅ Merge abgeschlossen:")
        print(f"      Vorher: {len(self.df_existing) if self.df_existing is not None else 0} Ziehungen")
        print(f"      Neue: {new_entries} Ziehungen")
        print(f"      Gesamt: {len(df_combined)} Ziehungen")

        return df_combined

    def save_data(self, df: pd.DataFrame) -> bool:
        """Speichert Daten."""
        try:
            print(f"\n💾 Speichere Daten...")

            df_export = df.copy()
            df_export['datum'] = df_export['datum'].dt.strftime('%Y-%m-%d')

            column_order = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'SZ']
            df_export = df_export[column_order]

            df_export.to_csv(self.data_file, sep=';', index=False)

            print(f"   ✅ Gespeichert: {self.data_file}")
            print(f"   📊 {len(df)} Ziehungen total")

            if len(df) > 0:
                latest = df['datum'].max()
                oldest = df['datum'].min()
                print(f"   📅 Zeitraum: {oldest.strftime('%Y-%m-%d')} bis {latest.strftime('%Y-%m-%d')}")

            return True

        except Exception as e:
            print(f"   ❌ Speicherfehler: {e}")

            if self.backup_file and os.path.exists(self.backup_file):
                print("   🔄 Stelle Backup wieder her...")
                shutil.copy2(self.backup_file, self.data_file)
                print("   ✅ Backup wiederhergestellt")

            return False

    def update(self, max_pages: int = 5, create_backup: bool = True) -> bool:
        """
        Führt komplettes Update durch.

        Args:
            max_pages: Maximale Anzahl Seiten zum Scrapen
            create_backup: Backup vor Update erstellen

        Returns:
            True bei Erfolg
        """
        print("\n🚀 STARTE UPDATE")
        print("=" * 70)

        if not self.load_existing_data():
            return False

        if create_backup:
            self.create_backup()

        new_draws = self.fetch_draws_from_web(max_pages=max_pages)

        if not new_draws:
            print("\n❌ Keine Daten von Website geladen")
            print("💡 Tipp: Verwende simple_update.py für manuelle Eingabe")
            return False

        df_updated = self.merge_with_existing(new_draws)
        success = self.save_data(df_updated)

        print("\n" + "=" * 70)
        if success:
            print("✅ UPDATE ERFOLGREICH ABGESCHLOSSEN")
        else:
            print("❌ UPDATE FEHLGESCHLAGEN")
        print("=" * 70)

        return success


def main():
    """Hauptfunktion."""
    import sys

    print()
    print("=" * 70)
    print("  LOTTO 6AUS49 WEB UPDATER")
    print("  Quelle: lotto.de")
    print("=" * 70)
    print()

    default_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/data/AlleLottozahlen.csv"

    if len(sys.argv) > 1:
        data_file = sys.argv[1]
        max_pages = int(sys.argv[2]) if len(sys.argv) > 2 else 5
    else:
        print(f"Standard-Datei: {default_file}")
        use_default = input("\nStandard verwenden? (j/n): ").strip().lower()

        if use_default in ['j', 'ja', 'y', 'yes', '']:
            data_file = default_file
        else:
            data_file = input("Pfad zur Datei: ").strip()

        pages_input = input("\nAnzahl Seiten laden (1-10, default=5): ").strip()
        max_pages = int(pages_input) if pages_input else 5

    print()

    updater = LottoWebUpdater(data_file)
    success = updater.update(max_pages=max_pages)

    sys.exit(0 if success else 1)


if __name__ == "__main__":
    main()
