import csv
import re
import math
from google.cloud import bigquery

# Optional imports
try:
    import pandas as pd
    from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE
except ImportError:
    pd = None
    ILLEGAL_CHARACTERS_RE = None

try:
    import gspread
    import google.auth
    from gspread.exceptions import APIError
except ImportError:
    gspread = None

class PiExporter:
    
    @staticmethod
    def _clean_for_excel(text):
        """Removes characters that cause Excel to crash/corrupt."""
        if not isinstance(text, str):
            return text
        if ILLEGAL_CHARACTERS_RE:
            return ILLEGAL_CHARACTERS_RE.sub('', text)
        return text

    @staticmethod
    def to_csv(data, filename):
        if not data:
            print("No data to export.")
            return
        
        if not filename.endswith('.csv'):
            filename += '.csv'
            
        keys = data[0].keys()
        with open(filename, 'w', newline='', encoding='utf-8') as f:
            writer = csv.DictWriter(f, fieldnames=keys)
            writer.writeheader()
            writer.writerows(data)
        print(f"Successfully saved {len(data)} rows to {filename}")

    @staticmethod
    def to_excel(data, filename):
        """Exports to Excel with sanitization."""
        if pd is None:
            print("Error: Pandas/Openpyxl not installed.")
            return
        if not data:
            print("No data to export.")
            return

        if not filename.endswith('.xlsx'):
            filename += '.xlsx'

        print(f"Preparing Excel file: {filename} ({len(data)} rows)...")

        try:
            df = pd.DataFrame(data)

            # Sanitize Data (Fixes "Excel found a problem with content")
            for col in df.select_dtypes(include=['object']).columns:
                df[col] = df[col].apply(PiExporter._clean_for_excel)

            df.to_excel(filename, index=False, engine='openpyxl')
            print(f"Successfully saved {len(data)} rows to {filename}")
            
        except Exception as e:
            print(f"Excel Export Failed: {e}")

    @staticmethod
    def to_google_sheet(data, spreadsheet_name, tab_name="Sheet1"):
        if gspread is None:
            print("Error: gspread not installed.")
            return
        if not data:
            print("No data to upload.")
            return

        row_count = len(data)
        print(f"Preparing Google Sheet upload: {row_count} rows...")

        try:
            # 1. Auth
            creds, _ = google.auth.default()
            client = gspread.authorize(creds)

            # 2. Open Sheet
            try:
                sh = client.open(spreadsheet_name)
            except gspread.SpreadsheetNotFound:
                print(f"Sheet '{spreadsheet_name}' not found. Creating it...")
                sh = client.create(spreadsheet_name)

            # 3. Setup Tab
            try:
                worksheet = sh.worksheet(tab_name)
                worksheet.clear()
            except gspread.WorksheetNotFound:
                worksheet = sh.add_worksheet(title=tab_name, rows=row_count+100, cols=20)

            # 4. Prepare Data
            headers = list(data[0].keys())
            rows = [[row.get(col, '') for col in headers] for row in data]
            
            # 5. Upload Headers
            worksheet.update([headers], 'A1')

            # 6. CHUNKED UPLOAD (Optimized for Speed)
            # 30,000 is the sweet spot. 
            # 90k often causes "Payload Too Large" or "Timeout" errors.
            chunk_size = 30000 
            total_chunks = math.ceil(len(rows) / chunk_size)

            print(f"Starting upload in {total_chunks} chunks (Size: {chunk_size})...")

            for i in range(total_chunks):
                start = i * chunk_size
                end = start + chunk_size
                chunk = rows[start:end]
                
                try:
                    # value_input_option='RAW' is faster than 'USER_ENTERED'
                    worksheet.append_rows(chunk, value_input_option='RAW')
                    print(f"   - Uploaded chunk {i+1}/{total_chunks} ({len(chunk)} rows)")
                except APIError as e:
                    print(f"   ❌ Error on chunk {i+1}: {e}")
                    print("      (If this is a timeout, try reducing chunk_size in the code)")
                    raise e

            print(f"✅ Successfully uploaded {row_count} rows to '{spreadsheet_name}' (Tab: {tab_name})")

        except Exception as e:
            print(f"❌ Google Sheet Upload Failed: {e}")

    @staticmethod
    def to_bigquery(data, project_id, dataset_id, table_id):
        if not data:
            print("No data to upload.")
            return

        client = bigquery.Client(project=project_id)
        table_ref = f"{project_id}.{dataset_id}.{table_id}"
        
        print(f"Uploading {len(data)} rows to BigQuery table {table_ref}...")
        
        job_config = bigquery.LoadJobConfig(
            autodetect=True,
            source_format=bigquery.SourceFormat.NEWLINE_DELIMITED_JSON,
            write_disposition=bigquery.WriteDisposition.WRITE_APPEND
        )

        try:
            job = client.load_table_from_json(data, table_ref, job_config=job_config)
            job.result() 
            print(f"Upload successful. Loaded {job.output_rows} rows.")
        except Exception as e:
            print(f"BigQuery Upload Failed: {e}")
            if hasattr(e, 'errors'):
                print(e.errors)