"""
Google Drive uploader for BizNode backups.
Handles OAuth2 authentication and file upload to a dedicated backup folder.
"""

import os
import json
import logging
from pathlib import Path
from datetime import datetime

from google.oauth2.credentials import Credentials
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload

logger = logging.getLogger("biznode.backup.gdrive")

SCOPES = ["https://www.googleapis.com/auth/drive.file"]
BACKUP_DIR = Path(__file__).parent
TOKEN_PATH = BACKUP_DIR / "token.json"
CREDENTIALS_PATH = BACKUP_DIR / "credentials.json"
GDRIVE_FOLDER_NAME = "BizNode_Backups"


def get_drive_service():
    """Authenticate and return a Google Drive API service instance."""
    creds = None

    if TOKEN_PATH.exists():
        creds = Credentials.from_authorized_user_file(str(TOKEN_PATH), SCOPES)

    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            if not CREDENTIALS_PATH.exists():
                raise FileNotFoundError(
                    f"Google OAuth credentials not found at {CREDENTIALS_PATH}\n"
                    "1. Go to https://console.cloud.google.com/apis/credentials\n"
                    "2. Create an OAuth 2.0 Client ID (Desktop app)\n"
                    "3. Download the JSON and save it as backup/credentials.json\n"
                    "4. Run: python backup/setup_gdrive.py"
                )
            flow = InstalledAppFlow.from_client_secrets_file(
                str(CREDENTIALS_PATH), SCOPES
            )
            # Try local server first, fall back to console URL if browser won't open
            try:
                creds = flow.run_local_server(port=8085, open_browser=True)
            except Exception:
                creds = flow.run_local_server(port=8085, open_browser=False)

        TOKEN_PATH.write_text(creds.to_json())
        logger.info("Google Drive token saved")

    return build("drive", "v3", credentials=creds)


def get_or_create_folder(service, folder_name, parent_id=None):
    """Find or create a folder in Google Drive. Returns folder ID."""
    query = (
        f"name='{folder_name}' and mimeType='application/vnd.google-apps.folder' "
        f"and trashed=false"
    )
    if parent_id:
        query += f" and '{parent_id}' in parents"

    results = service.files().list(q=query, spaces="drive", fields="files(id, name)").execute()
    files = results.get("files", [])

    if files:
        return files[0]["id"]

    metadata = {
        "name": folder_name,
        "mimeType": "application/vnd.google-apps.folder",
    }
    if parent_id:
        metadata["parents"] = [parent_id]

    folder = service.files().create(body=metadata, fields="id").execute()
    logger.info(f"Created Google Drive folder: {folder_name}")
    return folder["id"]


def upload_file(service, local_path, folder_id, filename=None):
    """Upload a file to a specific Google Drive folder."""
    if not os.path.exists(local_path):
        raise FileNotFoundError(f"File not found: {local_path}")

    fname = filename or os.path.basename(local_path)
    file_metadata = {"name": fname, "parents": [folder_id]}

    media = MediaFileUpload(local_path, resumable=True)
    uploaded = (
        service.files()
        .create(body=file_metadata, media_body=media, fields="id, name, size")
        .execute()
    )
    size_mb = int(uploaded.get("size", 0)) / (1024 * 1024)
    logger.info(f"Uploaded {uploaded['name']} ({size_mb:.2f} MB) -> ID: {uploaded['id']}")
    return uploaded


def cleanup_old_backups(service, folder_id, keep_days=30):
    """Delete backup files older than keep_days from the Google Drive folder."""
    from datetime import timedelta, timezone

    cutoff = datetime.now(timezone.utc) - timedelta(days=keep_days)
    cutoff_str = cutoff.isoformat()

    query = (
        f"'{folder_id}' in parents and trashed=false "
        f"and createdTime < '{cutoff_str}'"
    )
    results = service.files().list(
        q=query, spaces="drive", fields="files(id, name, createdTime)"
    ).execute()
    files = results.get("files", [])

    for f in files:
        service.files().delete(fileId=f["id"]).execute()
        logger.info(f"Deleted old backup: {f['name']} (created {f['createdTime']})")

    if files:
        logger.info(f"Cleaned up {len(files)} old backups (older than {keep_days} days)")
    return len(files)


def upload_backup_to_gdrive(local_files, date_str=None):
    """
    Upload a list of local backup files to Google Drive.
    Creates folder structure: BizNode_Backups / YYYY-MM-DD /
    Returns list of uploaded file info dicts.
    """
    if date_str is None:
        date_str = datetime.now().strftime("%Y-%m-%d")

    service = get_drive_service()
    root_folder_id = get_or_create_folder(service, GDRIVE_FOLDER_NAME)
    day_folder_id = get_or_create_folder(service, date_str, parent_id=root_folder_id)

    uploaded = []
    for fpath in local_files:
        try:
            result = upload_file(service, fpath, day_folder_id)
            uploaded.append(result)
        except Exception as e:
            logger.error(f"Failed to upload {fpath}: {e}")

    # Clean up backups older than 30 days
    try:
        cleanup_old_backups(service, root_folder_id, keep_days=30)
    except Exception as e:
        logger.warning(f"Cleanup of old backups failed: {e}")

    return uploaded
