# --- START OF FILE google_sheet_handler.py ---

#!/usr/bin/env python3
"""
google_sheet_handler.py

Klasse zur Kapselung der Interaktionen mit dem Google Sheet.
Stellt Verbindung her, lädt Daten und führt Batch-Updates durch.
"""

import os
import time
import logging
import traceback
from datetime import datetime

import gspread
from oauth2client.service_account import ServiceAccountCredentials

# Import der abhängigen Module
from config import Config, CREDENTIALS_FILE, COLUMN_MAP
from helpers import retry_on_failure

class GoogleSheetHandler:
    """
    Kapselt die Interaktionen mit dem Google Sheet, inklusive Verbindung,
    Daten laden und Batch-Updates. Nutzt den retry_on_failure Decorator.
    """
    def __init__(self):
        """
        Initialisiert den Handler, stellt die Verbindung her und laedt die Daten.
        """
        self.logger = logging.getLogger(__name__ + ".GoogleSheetHandler")
        self.sheet = None
        self._all_data = [] # KORREKTUR: Variable umbenannt
        self._header_rows = 5 # Annahme: Die ersten 5 Zeilen sind Header

        self.logger.info("Initialisiere GoogleSheetHandler...")
        try:
            self._connect()
            if self.sheet:
                self.load_data()
            else:
                self.logger.critical(
                    "GoogleSheetHandler Init FEHLER: Verbindung konnte nicht hergestellt werden (sheet ist None)."
                )
                raise ConnectionError("Google Sheet Handler Init failed: Verbindung konnte nicht hergestellt werden.")
        except Exception as e:
            self.logger.critical(f"FATAL: Fehler bei Initialisierung von GoogleSheetHandler: {type(e).__name__} - {e}")
            self.logger.debug(traceback.format_exc())
            raise ConnectionError(f"Google Sheet Handler Init failed: {e}")

    @retry_on_failure
    def _connect(self):
        """Stellt Verbindung zum Google Sheet her."""
        self.sheet = None
        self.logger.info("Versuche Verbindung mit Google Sheets herstellen...")
        try:
            if not os.path.exists(CREDENTIALS_FILE):
                raise FileNotFoundError(f"Credential-Datei nicht gefunden: {CREDENTIALS_FILE}")

            scope = ["https://www.googleapis.com/auth/spreadsheets"]
            creds = ServiceAccountCredentials.from_json_keyfile_name(CREDENTIALS_FILE, scope)
            gc = gspread.authorize(creds)
            sh = gc.open_by_url(Config.SHEET_URL)
            self.sheet = sh.sheet1
            self.logger.info("Verbindung zu Google Sheets erfolgreich.")
        except Exception as e:
            self.logger.error(f"FEHLER bei der Google Sheets Verbindung: {type(e).__name__} - {e}")
            self.logger.debug(traceback.format_exc())
            raise e

    @retry_on_failure
    def load_data(self):
        """
        Laedt alle Daten aus dem Sheet und aktualisiert die internen Datenstrukturen.
        Führt eine Validierung der Spaltenanzahl durch.
        """
        if not self.sheet:
            self.logger.error("Fehler: Keine Sheet-Verbindung zum Laden der Daten.")
            self._all_data = []
            return False

        self.logger.info("Lade Daten aus Google Sheet...")
        try:
            self._all_data = self.sheet.get_all_values()

            if not self._all_data:
                self.logger.warning("Google Sheet scheint leer zu sein.")
                self.headers = []
                return True

            num_rows = len(self._all_data)
            num_cols = len(self._all_data[0]) if num_rows > 0 else 0
            self.logger.info(f"Daten neu geladen: {num_rows} Zeilen, {num_cols} Spalten.")

            try:
                all_indices = [v['index'] for v in COLUMN_MAP.values()]
                if not all_indices: raise ValueError("COLUMN_MAP leer")
                
                max_col_idx_in_map = max(all_indices)
                expected_min_cols = max_col_idx_in_map + 1
                if num_cols < expected_min_cols:
                    self.logger.warning(
                        f"Sheet hat nur {num_cols} Spalten, aber COLUMN_MAP erwartet mind. {expected_min_cols}.")
            except Exception as e:
                self.logger.error(f"Fehler bei der Pruefung der Spaltenanzahl: {e}")

            for i, row in enumerate(self._all_data):
                if "CRM Name" in row:
                    self._header_rows = i + 1
                    self.headers = row
                    break
            else:
                self.headers = self._all_data[0] if self._all_data else []

            return True
        except Exception as e:
            self.logger.critical(f"Allgemeiner Fehler beim Laden der Google Sheet Daten: {e}", exc_info=True)
            return False

    def get_data(self):
        """
        Gibt die aktuell im Handler gespeicherten Datenzeilen zurueck
        (ohne die ersten N Header-Zeilen).
        """
        if not self._all_data or len(self._all_data) <= self._header_rows:
            self.logger.debug(
                f"get_data: Keine Datenzeilen verfuegbar "
                f"(geladen: {len(self._all_data) if self._all_data else 0} Zeilen, "
                f"{self._header_rows} Header)."
            )
            return []
        return self._all_data[self._header_rows:].copy()

    def get_all_data_with_headers(self):
        """Gibt alle aktuell im Handler gespeicherten Daten inklusive Header zurueck."""
        if not self._all_data:
            self.logger.debug("get_all_data_with_headers: Keine Daten im Handler gespeichert.")
            return []
        return self._all_data.copy()

    def _get_col_letter(self, col_idx_1_based):
        """
        Konvertiert einen 1-basierten Spaltenindex in den entsprechenden
        Google Sheets Spaltenbuchstaben (A, B, ..., Z, AA, ...).
        """
        if not isinstance(col_idx_1_based, int) or col_idx_1_based < 1:
            self.logger.error(f"Ungueltiger Spaltenindex ({col_idx_1_based}) fuer _get_col_letter erhalten.")
            return None

        string = ""
        n = col_idx_1_based
        while n > 0:
            n, remainder = divmod(n - 1, 26)
            string = chr(65 + remainder) + string
        return string

    def get_start_row_index(self, check_column_key, min_sheet_row=7):
        """
        Findet den 0-basierten Index in der DATENliste (ohne Header),
        ab einer Mindestzeilennummer im Sheet, in der der Wert in der
        Spalte (definiert durch check_column_key) EXAKT LEER ("") ist.
        """
        # Daten müssen nicht extra geladen werden, da dies im aufrufenden Prozess geschieht.
        data_rows = self.get_data()
        if not data_rows:
            self.logger.info("Keine Datenzeilen im Sheet gefunden. Startindex fuer leere Zelle ist 0.")
            return 0

        # KORREKTUR: Greife auf den 'index'-Wert zu
        col_info = COLUMN_MAP.get(check_column_key)
        if col_info is None or 'index' not in col_info:
            self.logger.critical(f"FEHLER: Schluessel '{check_column_key}' oder sein 'index' nicht in COLUMN_MAP gefunden!")
            return -1
        
        check_column_index = col_info['index']

        actual_col_letter = self._get_col_letter(check_column_index + 1)
        if actual_col_letter is None:
            actual_col_letter = f"Index_{check_column_index + 1}"

        search_start_index_in_data = max(0, (min_sheet_row - 1) - self._header_rows)
        self.logger.info(
            f"get_start_row_index: Suche ab Daten-Index {search_start_index_in_data} "
            f"(Sheet-Zeile {search_start_index_in_data + self._header_rows + 1}) "
            f"nach EXAKT LEEREM Wert (=='') in Spalte '{check_column_key}' ({actual_col_letter})..."
        )

        if search_start_index_in_data >= len(data_rows):
            self.logger.warning(
                f"Start-Suchindex in Daten ({search_start_index_in_data}) liegt hinter der letzten Datenzeile ({len(data_rows)})."
            )
            return len(data_rows)

        for i in range(search_start_index_in_data, len(data_rows)):
            row = data_rows[i]
            current_sheet_row = i + self._header_rows + 1
            is_exactly_empty = True
            if len(row) > check_column_index:
                cell_value = row[check_column_index]
                if cell_value != "":
                    is_exactly_empty = False
            
            if is_exactly_empty:
                self.logger.info(
                    f"Erste Zeile ab Sheet-Zeile {min_sheet_row} mit EXAKT LEEREM Wert in Spalte "
                    f"{actual_col_letter} gefunden: Zeile {current_sheet_row} (Daten-Index {i})"
                )
                return i

        last_data_index = len(data_rows)
        self.logger.info(
            f"Alle Zeilen ab Daten-Index {search_start_index_in_data} haben einen "
            f"nicht-leeren Wert in Spalte {actual_col_letter}. Naechster Daten-Index waere {last_data_index}."
        )
        return last_data_index

    def clear_and_write_data(self, sheet_name, data):
        """
        Leert das angegebene Tabellenblatt vollständig und schreibt neue Daten hinein.
        Die Daten sollten eine Liste von Listen sein (inklusive Header).
        """
        try:
            # NEU: Prüfen, ob eine Verbindung besteht, und ggf. herstellen
            if not self.client:
                if not self._connect():
                    self.logger.error("Verbindung zu Google Sheets konnte nicht hergestellt werden. Breche Schreibvorgang ab.")
                    return False

            self.logger.info(f"Greife auf Tabellenblatt '{sheet_name}' zu, um es zu leeren und neu zu beschreiben...")
            worksheet = self.client.open_by_url(self.sheet_url).worksheet(sheet_name)
            
            self.logger.debug("Leere das gesamte Tabellenblatt...")
            worksheet.clear()
            
            num_rows = len(data)
            num_cols = len(data[0]) if data else 0
            
            if num_rows == 0:
                self.logger.warning("Keine Daten zum Schreiben vorhanden.")
                return True

            self.logger.info(f"Schreibe {num_rows - 1} neue Datenzeilen (insgesamt {num_rows} Zeilen mit Header) in '{sheet_name}'...")
            
            end_col_letter = self._get_col_letter(num_cols)
            range_to_update = f'A1:{end_col_letter}{num_rows}'

            worksheet.update(range_name=range_to_update, values=data)
            self.logger.info(f"Schreiben in Tabellenblatt '{sheet_name}' erfolgreich abgeschlossen.")
            return True
        except gspread.exceptions.WorksheetNotFound:
            self.logger.error(f"FATAL: Das Tabellenblatt '{sheet_name}' wurde nicht gefunden. Bitte prüfen Sie den Namen.")
            return False
        except Exception as e:
            self.logger.error(f"FATAL: Ein unerwarteter Fehler ist beim Schreiben in '{sheet_name}' aufgetreten: {e}")
            return False
            

    @retry_on_failure
    def batch_update_cells(self, update_data):
        """
        Fuehrt ein Batch-Update im Google Sheet durch.
        NEU: Konvertiert alle zu schreibenden Werte explizit in Strings, um Fehler zu vermeiden.
        """
        if not self.sheet:
            self.logger.error("FEHLER: Keine Sheet-Verbindung fuer Batch-Update.")
            return False
        if not update_data:
            self.logger.debug("batch_update_cells: Keine Daten zum Aktualisieren erhalten.")
            return True

        # --- NEUE, ROBUSTE DATENAUFBEREITUNG ---
        sanitized_update_data = []
        for item in update_data:
            if 'range' in item and 'values' in item and isinstance(item['values'], list):
                # Konvertiere jeden einzelnen Zellwert im values-Array sicher in einen String
                sanitized_values = [
                    [str(cell_value) if cell_value is not None else "" for cell_value in row]
                    for row in item['values']
                ]
                sanitized_update_data.append({
                    'range': item['range'],
                    'values': sanitized_values
                })
            else:
                self.logger.warning(f"Überspringe ungültiges Update-Objekt: {item}")

        if not sanitized_update_data:
            self.logger.warning("Keine gültigen Daten nach der Bereinigung für das Batch-Update übrig.")
            return True
        
        try:
            total_cells_to_update = sum(len(row) for item in sanitized_update_data for row in item.get('values', []))
            self.logger.debug(f"  -> Versuche sheet.batch_update mit {len(sanitized_update_data)} Anfragen ({total_cells_to_update} Zellen)...")
            
            # Logge das erste Datenobjekt zur Überprüfung
            if self.logger.level == logging.DEBUG and sanitized_update_data:
                 self.logger.debug(f"    -> Beispiel-Update-Daten: {str(sanitized_update_data[0])}")

            self.sheet.batch_update(sanitized_update_data, value_input_option='USER_ENTERED')
            self.logger.info(f"Batch-Update mit {total_cells_to_update} Zellen erfolgreich gesendet.")
            return True
        except Exception as e:
            self.logger.error(f"Endgueltiger Fehler beim Batch-Update nach Retries: {e}", exc_info=True)
            return False

# --- END OF FILE google_sheet_handler.py ---