#!/usr/bin/env python3
"""
CSV-Validator für Lotto 6aus49 und Eurojackpot

Validiert CSV-Dateien auf:
- Korrekte Spaltenstruktur
- Datumformat und -konsistenz
- Zahlenbereich und Eindeutigkeit
- Wochentag (nur Mi/Sa für Lotto, nur Di/Fr für Eurojackpot)
- Chronologische Sortierung
- Duplikate
- Fehlende Werte
- Zukunftsdaten
"""

import pandas as pd
from datetime import datetime
import sys
import os
from typing import List, Dict, Tuple


class LottoCSVValidator:
    """Validator für Lotto 6aus49 CSV-Dateien."""

    def __init__(self, csv_file: str):
        self.csv_file = csv_file
        self.df = None
        self.errors = []
        self.warnings = []
        self.lottery_type = self._detect_lottery_type()

    def _detect_lottery_type(self) -> str:
        """Erkennt ob Lotto oder Eurojackpot basierend auf Dateiname."""
        basename = os.path.basename(self.csv_file).lower()
        if 'eurojackpot' in basename:
            return 'eurojackpot'
        elif 'lotto' in basename:
            return 'lotto'
        else:
            # Versuche anhand der Spalten zu erkennen
            return 'unknown'

    def load_csv(self) -> bool:
        """Lädt CSV-Datei."""
        try:
            if not os.path.exists(self.csv_file):
                self.errors.append(f"❌ Datei existiert nicht: {self.csv_file}")
                return False

            self.df = pd.read_csv(self.csv_file, sep=';')

            # Auto-detect wenn noch unknown
            if self.lottery_type == 'unknown':
                if 'SZ2' in self.df.columns:
                    self.lottery_type = 'eurojackpot'
                elif 'SZ' in self.df.columns:
                    self.lottery_type = 'lotto'
                else:
                    self.errors.append("❌ Kann Lottery-Typ nicht erkennen")
                    return False

            return True

        except Exception as e:
            self.errors.append(f"❌ Fehler beim Laden: {e}")
            return False

    def validate_structure(self) -> bool:
        """Validiert Spaltenstruktur."""
        if self.lottery_type == 'lotto':
            expected_cols = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'SZ']
        elif self.lottery_type == 'eurojackpot':
            expected_cols = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'SZ1', 'SZ2']
        else:
            self.errors.append("❌ Unbekannter Lottery-Typ")
            return False

        actual_cols = list(self.df.columns)

        if actual_cols != expected_cols:
            self.errors.append(f"❌ Spaltenstruktur falsch")
            self.errors.append(f"   Erwartet: {expected_cols}")
            self.errors.append(f"   Gefunden: {actual_cols}")
            return False

        return True

    def validate_dates(self) -> bool:
        """Validiert Datumsspalte."""
        valid = True

        # Prüfe auf leere Daten
        empty_dates = self.df[self.df['datum'].isna() | (self.df['datum'] == '')]
        if len(empty_dates) > 0:
            self.errors.append(f"❌ {len(empty_dates)} Zeile(n) mit fehlendem Datum:")
            for idx in empty_dates.index[:5]:  # Zeige max 5
                self.errors.append(f"   Zeile {idx + 2}")
            if len(empty_dates) > 5:
                self.errors.append(f"   ... und {len(empty_dates) - 5} weitere")
            valid = False

        # Konvertiere Datum
        try:
            self.df['datum'] = pd.to_datetime(self.df['datum'], format='%Y-%m-%d', errors='coerce')
        except Exception as e:
            self.errors.append(f"❌ Fehler beim Parsen der Datumsangaben: {e}")
            return False

        # Prüfe auf ungültige Datumsangaben
        invalid_dates = self.df[self.df['datum'].isna()]
        if len(invalid_dates) > 0:
            self.errors.append(f"❌ {len(invalid_dates)} ungültige Datumsangaben")
            valid = False

        # Prüfe auf Zukunftsdaten
        today = datetime.now()
        future_dates = self.df[self.df['datum'] > today]
        if len(future_dates) > 0:
            self.errors.append(f"❌ {len(future_dates)} Datum/Daten in der Zukunft:")
            for idx, row in future_dates.iterrows():
                self.errors.append(f"   Zeile {idx + 2}: {row['datum'].strftime('%Y-%m-%d')}")
            valid = False

        # Prüfe Wochentage
        if self.lottery_type == 'lotto':
            # Historisch:
            # 1955-1965: Sonntags (und manchmal Montags bei Feiertagen)
            # 1965-2000: Samstags
            # Ab 2000: Mittwoch + Samstag
            weekday_names = {0: 'Mo', 1: 'Di', 2: 'Mi', 3: 'Do', 4: 'Fr', 5: 'Sa', 6: 'So'}
        else:  # eurojackpot
            # Eurojackpot: Dienstag + Freitag
            weekday_names = {0: 'Mo', 1: 'Di', 2: 'Mi', 3: 'Do', 4: 'Fr', 5: 'Sa', 6: 'So'}

        wrong_weekdays = []
        for idx, row in self.df.iterrows():
            if pd.notna(row['datum']):
                weekday = row['datum'].weekday()
                date = row['datum']

                if self.lottery_type == 'lotto':
                    # Lotto: Historische Regeln
                    if date < datetime(1965, 9, 1):
                        # Bis Aug 1965: Sonntag (und Montag bei Feiertagen)
                        if weekday not in [0, 6]:  # Mo, So
                            wrong_weekdays.append((idx, date))
                    elif date < datetime(2000, 12, 1):
                        # Sep 1965 - Nov 2000: Nur Samstag
                        if weekday != 5:  # Sa
                            wrong_weekdays.append((idx, date))
                    else:
                        # Ab Dez 2000: Mittwoch + Samstag
                        if weekday not in [2, 5]:  # Mi, Sa
                            wrong_weekdays.append((idx, date))
                else:
                    # Eurojackpot: Dienstag + Freitag
                    if weekday not in [1, 4]:  # Di, Fr
                        wrong_weekdays.append((idx, date))

        if len(wrong_weekdays) > 0:
            # Filtere mögliche Feiertags-Sonderziehungen (< 5 pro Zeitraum = OK)
            if len(wrong_weekdays) <= 5:
                self.warnings.append(f"⚠️  {len(wrong_weekdays)} Ziehung(en) am ungewöhnlichen Wochentag (möglicherweise Feiertage):")
                for idx, date in wrong_weekdays:
                    weekday_name = weekday_names[date.weekday()]
                    self.warnings.append(f"   Zeile {idx + 2}: {date.strftime('%Y-%m-%d')} ({weekday_name})")
            else:
                self.errors.append(f"❌ {len(wrong_weekdays)} Ziehung(en) am falschen Wochentag:")
                for idx, date in wrong_weekdays[:5]:
                    weekday_name = weekday_names[date.weekday()]
                    self.errors.append(f"   Zeile {idx + 2}: {date.strftime('%Y-%m-%d')} ({weekday_name})")
                if len(wrong_weekdays) > 5:
                    self.errors.append(f"   ... und {len(wrong_weekdays) - 5} weitere")
                valid = False

        return valid

    def validate_numbers(self) -> bool:
        """Validiert Zahlenwerte."""
        valid = True

        if self.lottery_type == 'lotto':
            main_cols = ['Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6']
            main_range = (1, 49)
            sz_cols = ['SZ']
            sz_range = (0, 9)
        else:  # eurojackpot
            main_cols = ['Z1', 'Z2', 'Z3', 'Z4', 'Z5']
            main_range = (1, 50)
            sz_cols = ['SZ1', 'SZ2']
            sz_range = (1, 12)

        # Prüfe Hauptzahlen
        for col in main_cols:
            # Fehlende Werte
            missing = self.df[self.df[col].isna()]
            if len(missing) > 0:
                self.errors.append(f"❌ {len(missing)} fehlende Werte in Spalte {col}")
                valid = False

            # Zahlenbereich
            out_of_range = self.df[
                (self.df[col] < main_range[0]) |
                (self.df[col] > main_range[1])
            ]
            if len(out_of_range) > 0:
                self.errors.append(f"❌ {len(out_of_range)} Werte außerhalb {main_range} in {col}")
                for idx, row in out_of_range.head(3).iterrows():
                    self.errors.append(f"   Zeile {idx + 2}: {row[col]}")
                valid = False

        # Prüfe Superzahl(en)
        for col in sz_cols:
            missing = self.df[self.df[col].isna()]
            if len(missing) > 0:
                self.warnings.append(f"⚠️  {len(missing)} fehlende Werte in Spalte {col}")

            out_of_range = self.df[
                (self.df[col] < sz_range[0]) |
                (self.df[col] > sz_range[1])
            ]
            if len(out_of_range) > 0:
                self.errors.append(f"❌ {len(out_of_range)} Werte außerhalb {sz_range} in {col}")
                valid = False

        # Prüfe Eindeutigkeit der Hauptzahlen pro Zeile
        duplicate_numbers = []
        for idx, row in self.df.iterrows():
            main_numbers = [row[col] for col in main_cols if pd.notna(row[col])]
            if len(main_numbers) != len(set(main_numbers)):
                duplicate_numbers.append((idx, main_numbers))

        if len(duplicate_numbers) > 0:
            self.errors.append(f"❌ {len(duplicate_numbers)} Zeile(n) mit doppelten Hauptzahlen:")
            for idx, numbers in duplicate_numbers[:5]:
                self.errors.append(f"   Zeile {idx + 2}: {numbers}")
            if len(duplicate_numbers) > 5:
                self.errors.append(f"   ... und {len(duplicate_numbers) - 5} weitere")
            valid = False

        # Prüfe Sortierung der Hauptzahlen pro Zeile
        unsorted_rows = []
        for idx, row in self.df.iterrows():
            main_numbers = [row[col] for col in main_cols if pd.notna(row[col])]
            if main_numbers != sorted(main_numbers):
                unsorted_rows.append((idx, main_numbers))

        if len(unsorted_rows) > 0:
            self.warnings.append(f"⚠️  {len(unsorted_rows)} Zeile(n) mit unsortierten Zahlen:")
            for idx, numbers in unsorted_rows[:3]:
                self.warnings.append(f"   Zeile {idx + 2}: {numbers}")

        # Prüfe Sortierung der Eurozahlen (nur Eurojackpot)
        if self.lottery_type == 'eurojackpot':
            unsorted_euro = []
            for idx, row in self.df.iterrows():
                if pd.notna(row['SZ1']) and pd.notna(row['SZ2']):
                    if row['SZ1'] > row['SZ2']:
                        unsorted_euro.append((idx, row['SZ1'], row['SZ2']))

            if len(unsorted_euro) > 0:
                self.warnings.append(f"⚠️  {len(unsorted_euro)} Zeile(n) mit unsortierten Eurozahlen")

        return valid

    def validate_duplicates(self) -> bool:
        """Prüft auf doppelte Datumssätze."""
        duplicates = self.df[self.df.duplicated(subset=['datum'], keep=False)]

        if len(duplicates) > 0:
            self.errors.append(f"❌ {len(duplicates)} doppelte Datumseinträge gefunden:")
            for idx, row in duplicates.head(5).iterrows():
                self.errors.append(f"   Zeile {idx + 2}: {row['datum'].strftime('%Y-%m-%d')}")
            return False

        return True

    def validate_chronology(self) -> bool:
        """Prüft chronologische Sortierung."""
        if len(self.df) < 2:
            return True

        unsorted = []
        for i in range(len(self.df) - 1):
            if self.df.iloc[i]['datum'] > self.df.iloc[i + 1]['datum']:
                unsorted.append((i, self.df.iloc[i]['datum'], self.df.iloc[i + 1]['datum']))

        if len(unsorted) > 0:
            self.warnings.append(f"⚠️  {len(unsorted)} Stelle(n) mit falscher chronologischer Reihenfolge:")
            for idx, date1, date2 in unsorted[:3]:
                self.warnings.append(f"   Zeile {idx + 2}: {date1.strftime('%Y-%m-%d')} > {date2.strftime('%Y-%m-%d')}")

        return len(unsorted) == 0

    def validate_completeness(self) -> bool:
        """Prüft auf Lücken in den Ziehungen."""
        if len(self.df) < 2:
            return True

        if self.lottery_type == 'lotto':
            # Lotto: 2x pro Woche (Mi, Sa)
            expected_days = 3.5  # Durchschnitt
        else:
            # Eurojackpot: 2x pro Woche (Di, Fr)
            expected_days = 3.5

        gaps = []
        for i in range(len(self.df) - 1):
            date1 = self.df.iloc[i]['datum']
            date2 = self.df.iloc[i + 1]['datum']
            diff = (date2 - date1).days

            # Wenn Lücke größer als 7 Tage, könnte Ziehung fehlen
            if diff > 7:
                gaps.append((i, date1, date2, diff))

        if len(gaps) > 0:
            self.warnings.append(f"⚠️  {len(gaps)} mögliche Lücke(n) in den Ziehungen (>7 Tage):")
            for idx, date1, date2, diff in gaps[:5]:
                self.warnings.append(f"   Zeile {idx + 2}: {date1.strftime('%Y-%m-%d')} → {date2.strftime('%Y-%m-%d')} ({diff} Tage)")
            if len(gaps) > 5:
                self.warnings.append(f"   ... und {len(gaps) - 5} weitere")

        return True

    def get_statistics(self) -> Dict:
        """Erstellt Statistiken."""
        if self.df is None or len(self.df) == 0:
            return {}

        stats = {
            'total_draws': len(self.df),
            'date_range': (
                self.df['datum'].min().strftime('%Y-%m-%d'),
                self.df['datum'].max().strftime('%Y-%m-%d')
            ),
            'years_covered': (self.df['datum'].max().year - self.df['datum'].min().year) + 1,
        }

        return stats

    def validate_all(self) -> bool:
        """Führt alle Validierungen durch."""
        print(f"\n{'='*70}")
        print(f"  CSV VALIDATOR")
        print(f"  Typ: {self.lottery_type.upper()}")
        print(f"{'='*70}")
        print(f"\n📁 Datei: {os.path.basename(self.csv_file)}")

        if not self.load_csv():
            return False

        print(f"✅ Datei geladen: {len(self.df)} Zeilen")

        # Alle Validierungen
        checks = [
            ("Spaltenstruktur", self.validate_structure),
            ("Datumsangaben", self.validate_dates),
            ("Zahlenwerte", self.validate_numbers),
            ("Duplikate", self.validate_duplicates),
            ("Chronologie", self.validate_chronology),
            ("Vollständigkeit", self.validate_completeness),
        ]

        print(f"\n🔍 VALIDIERUNG")
        print("="*70)

        all_valid = True
        for check_name, check_func in checks:
            try:
                result = check_func()
                status = "✅" if result else "❌"
                print(f"{status} {check_name}")
                if not result:
                    all_valid = False
            except Exception as e:
                print(f"❌ {check_name} - Fehler: {e}")
                all_valid = False

        # Statistiken
        stats = self.get_statistics()
        if stats:
            print(f"\n📊 STATISTIKEN")
            print("="*70)
            print(f"Ziehungen gesamt: {stats['total_draws']}")
            print(f"Zeitraum: {stats['date_range'][0]} bis {stats['date_range'][1]}")
            print(f"Jahre: {stats['years_covered']}")

        # Fehler ausgeben
        if self.errors:
            print(f"\n❌ FEHLER ({len(self.errors)})")
            print("="*70)
            for error in self.errors:
                print(error)

        # Warnungen ausgeben
        if self.warnings:
            print(f"\n⚠️  WARNUNGEN ({len(self.warnings)})")
            print("="*70)
            for warning in self.warnings:
                print(warning)

        # Zusammenfassung
        print(f"\n{'='*70}")
        if all_valid and not self.errors:
            print("✅ VALIDIERUNG ERFOLGREICH - Keine Fehler gefunden!")
        elif not self.errors and self.warnings:
            print("✅ VALIDIERUNG OK - Nur Warnungen (keine kritischen Fehler)")
        else:
            print("❌ VALIDIERUNG FEHLGESCHLAGEN")
        print("="*70)

        return all_valid and len(self.errors) == 0


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

    # Standard-Dateien
    default_files = {
        'lotto': '/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/data/AlleLottozahlen.csv',
        'eurojackpot': '/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/AlleEurojackpotzahlen.csv'
    }

    if len(sys.argv) > 1:
        # Benutzerdefinierte Datei
        csv_file = sys.argv[1]
        validator = LottoCSVValidator(csv_file)
        success = validator.validate_all()
        sys.exit(0 if success else 1)
    else:
        # Validiere beide Standard-Dateien
        print("\n🎲 Validiere beide Lottery-Dateien...\n")

        results = {}
        for lottery_type, csv_file in default_files.items():
            if os.path.exists(csv_file):
                validator = LottoCSVValidator(csv_file)
                results[lottery_type] = validator.validate_all()
            else:
                print(f"\n⚠️  {lottery_type.upper()}: Datei nicht gefunden: {csv_file}")
                results[lottery_type] = False

        # Gesamtergebnis
        print("\n" + "="*70)
        print("  GESAMTERGEBNIS")
        print("="*70)
        for lottery_type, success in results.items():
            status = "✅" if success else "❌"
            print(f"{status} {lottery_type.upper()}")
        print("="*70)

        all_success = all(results.values())
        sys.exit(0 if all_success else 1)


if __name__ == "__main__":
    main()
