"""
Google Drive API helpers.

Note: The service account must have access to the target Drive folders/files.
      Share folders with the service account email (e.g. mysa@project.iam.gserviceaccount.com).
"""

from __future__ import annotations

import csv
import io
import logging
import shutil
from typing import Any, Optional

from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from googleapiclient.http import MediaIoBaseDownload

from ._auth import get_credentials

logger = logging.getLogger(__name__)

SCOPES = ["https://www.googleapis.com/auth/drive"]
API_NAME = "drive"
API_VERSION = "v3"


def gdrive_get_service(
    *,
    path_keyfile: str | None = None,
    keyfile_json: dict | None = None,
    credentials: Any | None = None,
    scopes: list[str] | None = None,
):
    """
    Build a Google Drive API v3 service object.

    Credential priority:
      1) ``credentials``   — pre-resolved credentials object (e.g. from toolsbq)
      2) ``keyfile_json``  — SA key as dict
      3) ``path_keyfile``  — path to SA JSON file
      4) RAM-ADC / env / ADC fallback

    Args:
        path_keyfile:  Path to a service account JSON key file.
        keyfile_json:  Parsed service account JSON as dict.
        credentials:   Pre-resolved google credentials object.
        scopes:        Override default scopes (default: full Drive access).

    Returns:
        A Google Drive v3 service object.
    """
    creds = get_credentials(
        path_keyfile=path_keyfile,
        keyfile_json=keyfile_json,
        credentials=credentials,
        scopes=scopes or SCOPES,
    )
    return build(API_NAME, API_VERSION, credentials=creds)


def list_files(
    service,
    q_filter: str = "",
    fields: str = "id, name, mimeType, createdTime, modifiedTime, size, md5Checksum, trashed",
    limit_pull: int = 200,
    sort_order: str = "modifiedTime desc",
) -> list[dict]:
    """
    List files from Google Drive.

    Args:
        service:    Drive API service object (from gdrive_get_service).
        q_filter:   Drive API query filter string. Examples:
                      "mimeType='text/csv'"
                      "mimeType='text/csv' and trashed=False and name contains 'report'"
                      "'<FOLDER_ID>' in parents and modifiedTime > '2024-01-01'"
        fields:     Comma-separated file fields to return.
                    Options: id, name, mimeType, createdTime, modifiedTime, size,
                             parents, fullFileExtension, md5Checksum, trashed, ...
        limit_pull: Maximum total number of files to return.
        sort_order: Sort order, e.g. "modifiedTime desc", "createdTime asc".

    Returns:
        List of dicts, one per file, with the requested fields.
    """
    RESULTS_PER_PULL_DEFAULT = 100

    results_per_pull = min(limit_pull, RESULTS_PER_PULL_DEFAULT)
    items: list[dict] = []

    try:
        next_page_token = ""

        while True:
            response = (
                service.files()
                .list(
                    q=q_filter,
                    orderBy=sort_order,
                    pageToken=next_page_token,
                    spaces="drive",
                    pageSize=results_per_pull,
                    fields="nextPageToken, files({})".format(fields),
                )
                .execute()
            )

            items.extend(response.get("files", []))
            next_page_token = response.get("nextPageToken", "")
            if next_page_token == "" or len(items) >= limit_pull:
                break

    except HttpError as error:
        logger.error("An error occurred: %s", error)

    return items


def download_file(service, file_id: str) -> Optional[io.BytesIO]:
    """
    Download a file from Drive and return it as an in-memory BytesIO object.

    Args:
        service:  Drive API service object.
        file_id:  The Google Drive file ID.

    Returns:
        BytesIO object with file contents, or None on error.
    """
    try:
        request = service.files().get_media(fileId=file_id)
        fh = io.BytesIO()
        downloader = MediaIoBaseDownload(fh, request)
        done = False
        while not done:
            status, done = downloader.next_chunk()
            logger.info("Download %d%%", int(status.progress() * 100))
    except HttpError as error:
        logger.error("An error occurred: %s", error)
        fh = None

    return fh


def download_file_to_disk(service, file_id: str, filename_path: str) -> None:
    """
    Download a file from Drive and save it to disk.

    Args:
        service:       Drive API service object.
        file_id:       The Google Drive file ID.
        filename_path: Full target path including filename.
    """
    file_mem = download_file(service, file_id)
    if file_mem is not None:
        file_mem.seek(0)
        with open(filename_path, "wb") as f:
            shutil.copyfileobj(file_mem, f)
        logger.info("Saved file to disk: %s", filename_path)


def download_file_to_csv(
    service,
    file_id: str,
    decoding: str = "utf-8-sig",
    delimiter: str = ",",
) -> Optional[csv.DictReader]:
    """
    Download a CSV file from Drive and return a DictReader.

    Args:
        service:   Drive API service object.
        file_id:   The Google Drive file ID.
        decoding:  Text encoding (e.g. "utf-8-sig", "utf-8", "ascii").
        delimiter: CSV delimiter character.

    Returns:
        csv.DictReader object, or None on error.
    """
    file_mem = download_file(service, file_id)

    if file_mem is not None:
        file_mem.seek(0)
        byte_str = file_mem.read()
        text_obj = byte_str.decode(decoding)
        fo = io.StringIO(text_obj)
        reader = csv.DictReader(fo, delimiter=delimiter)
    else:
        reader = None

    return reader
