import os
import shutil
import sqlite3
import json
import threading
from paths import DATA_DIR
from datetime import datetime

from apscheduler.schedulers.background import BackgroundScheduler

# =====================
# PATHS
# =====================
BASE_DIR          = DATA_DIR
DB_PATH           = os.path.join(BASE_DIR, "shop.db")
LOCAL_BACKUP_DIR  = os.path.join(BASE_DIR, "backups", "local")
GDRIVE_TOKEN_FILE = os.path.join(BASE_DIR, "gdrive_token.json")
GDRIVE_CREDS_FILE = os.path.join(BASE_DIR, "gdrive_credentials.json")
MAX_LOCAL_BACKUPS = 7          # keep last 7 local copies
MAX_DRIVE_BACKUPS = 30         # keep last 30 in Google Drive folder

# Status that the UI can poll
_gdrive_status = {"connected": False, "last_upload": None, "error": None}


# =====================
# LOCAL BACKUP
# =====================
def create_local_backup():
    if not os.path.exists(DB_PATH):
        print("ERROR: Database file not found:", DB_PATH)
        return None

    os.makedirs(LOCAL_BACKUP_DIR, exist_ok=True)

    date        = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
    backup_file = os.path.join(LOCAL_BACKUP_DIR, f"backup_{date}.db")

    # sqlite's backup API gives a consistent snapshot even while POS is open
    with sqlite3.connect(DB_PATH) as src, sqlite3.connect(backup_file) as dst:
        src.backup(dst)

    print("Local backup created:", backup_file)
    _cleanup_local_backups()
    return backup_file


def _cleanup_local_backups():
    if not os.path.exists(LOCAL_BACKUP_DIR):
        return
    files = sorted(
        [f for f in os.listdir(LOCAL_BACKUP_DIR) if f.endswith(".db")],
        key=lambda x: os.path.getmtime(os.path.join(LOCAL_BACKUP_DIR, x))
    )
    while len(files) > MAX_LOCAL_BACKUPS:
        os.remove(os.path.join(LOCAL_BACKUP_DIR, files.pop(0)))


# =====================
# GOOGLE DRIVE HELPERS
# =====================
SCOPES = ["https://www.googleapis.com/auth/drive.file"]

def _get_gdrive_service():
    """Return an authenticated Drive service, or None if not configured."""
    try:
        from google.oauth2.credentials import Credentials
        from google.auth.transport.requests import Request
        from googleapiclient.discovery import build

        if not os.path.exists(GDRIVE_TOKEN_FILE):
            return None

        creds = Credentials.from_authorized_user_file(GDRIVE_TOKEN_FILE, SCOPES)

        # Refresh silently if expired
        if creds.expired and creds.refresh_token:
            creds.refresh(Request())
            with open(GDRIVE_TOKEN_FILE, "w") as f:
                f.write(creds.to_json())

        if not creds.valid:
            return None

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

    except Exception as e:
        print("GDrive auth error:", e)
        _gdrive_status["error"] = str(e)
        return None


def _get_or_create_folder(service, folder_name="ShopManagerPOS_Backups"):
    """Return the Drive folder ID, creating it if needed."""
    q = (
        f"name='{folder_name}' and mimeType='application/vnd.google-apps.folder'"
        " and trashed=false"
    )
    results = service.files().list(q=q, fields="files(id)").execute()
    files   = results.get("files", [])
    if files:
        return files[0]["id"]

    folder = service.files().create(
        body={"name": folder_name, "mimeType": "application/vnd.google-apps.folder"},
        fields="id"
    ).execute()
    return folder["id"]


def _cleanup_drive_backups(service, folder_id):
    """Keep only the latest MAX_DRIVE_BACKUPS files in the Drive folder."""
    results = service.files().list(
        q=f"'{folder_id}' in parents and trashed=false",
        orderBy="createdTime",
        fields="files(id, name)"
    ).execute()
    files = results.get("files", [])
    while len(files) > MAX_DRIVE_BACKUPS:
        service.files().delete(fileId=files.pop(0)["id"]).execute()


# =====================
# GOOGLE DRIVE UPLOAD
# =====================
def upload_to_gdrive(backup_file=None):
    global _gdrive_status
    try:
        from googleapiclient.http import MediaFileUpload

        service = _get_gdrive_service()
        if service is None:
            _gdrive_status["error"] = "Google Drive not connected"
            return False

        # If no specific file given, create a fresh local backup first
        if backup_file is None:
            backup_file = create_local_backup()
        if backup_file is None or not os.path.exists(backup_file):
            return False

        folder_id = _get_or_create_folder(service)
        file_name = os.path.basename(backup_file)

        media = MediaFileUpload(backup_file, mimetype="application/octet-stream", resumable=True)
        service.files().create(
            body={"name": file_name, "parents": [folder_id]},
            media_body=media,
            fields="id"
        ).execute()

        _cleanup_drive_backups(service, folder_id)

        _gdrive_status["connected"] = True
        _gdrive_status["last_upload"] = datetime.now().strftime("%d %b %Y, %I:%M %p")
        _gdrive_status["error"] = None
        print("Google Drive backup uploaded:", file_name)
        return True

    except Exception as e:
        _gdrive_status["error"] = str(e)
        print("Google Drive upload failed:", e)
        return False


# =====================
# FULL BACKUP (local + cloud)
# =====================
def full_backup():
    backup_file = create_local_backup()
    upload_to_gdrive(backup_file)


# =====================
# GOOGLE DRIVE CONNECT  (called from the settings page)
# Opens browser once for the customer to log in — never again after that.
# =====================
def connect_gdrive(client_secrets_json: dict) -> bool:
    """
    Takes the parsed client_secrets JSON that the shop owner uploads,
    runs the OAuth2 flow (opens browser once), and saves the token.
    Returns True on success.
    """
    try:
        from google_auth_oauthlib.flow import InstalledAppFlow

        flow = InstalledAppFlow.from_client_config(client_secrets_json, SCOPES)
        creds = flow.run_local_server(port=0, open_browser=True)

        os.makedirs(BASE_DIR, exist_ok=True)
        with open(GDRIVE_TOKEN_FILE, "w") as f:
            f.write(creds.to_json())

        _gdrive_status["connected"] = True
        _gdrive_status["error"]     = None
        print("Google Drive connected successfully.")
        return True

    except Exception as e:
        _gdrive_status["error"] = str(e)
        print("Google Drive connect failed:", e)
        return False


def disconnect_gdrive():
    if os.path.exists(GDRIVE_TOKEN_FILE):
        os.remove(GDRIVE_TOKEN_FILE)
    _gdrive_status["connected"]   = False
    _gdrive_status["last_upload"] = None
    _gdrive_status["error"]       = None


def gdrive_status() -> dict:
    svc = _get_gdrive_service()
    _gdrive_status["connected"] = svc is not None
    return dict(_gdrive_status)


# =====================
# SCHEDULER
# =====================
scheduler = BackgroundScheduler()

def sync_local_and_cloud_backups():
    """Executes synchronized local snapshot and cloud backup every 6 hours."""
    print("Executing 6-hour synchronized local and cloud database backup...")
    filepath = create_local_backup()
    if filepath:
        upload_to_gdrive(filepath=filepath)

# Synchronized backup every 6 hours
scheduler.add_job(sync_local_and_cloud_backups, trigger="interval", hours=6, id="sync_6hr_backup")


def start_backup_system():
    if os.environ.get("START_SCHEDULER") == "1":
        if not scheduler.running:
            scheduler.start()
            print("Backup system started with 6-hour synchronized backup interval.")


