import argparse
import json
import os.path
import re
import sys
import textwrap
from collections.abc import Sequence
from typing import Any

import requests

from . import __version__
from .download import GoogleDriveFileToDownload
from .download import download
from .download_folder import download_folder
from .exceptions import DownloadError


class _ShowVersionAction(argparse.Action):
    # The callback protocol requires the full signature even when values are unused.
    def __call__(
        self,
        parser: argparse.ArgumentParser,
        namespace: argparse.Namespace,  # noqa: ARG002
        values: str | Sequence[Any] | None,  # noqa: ARG002
        option_string: str | None = None,  # noqa: ARG002
    ) -> None:  # noqa: GR005 -- inherited protocol accepts both call styles
        print(f"gdown {__version__} at {os.path.dirname(os.path.dirname(__file__))}")
        parser.exit()


def file_size(argv: str | None) -> float | None:  # noqa: GR005 -- public API accepts both call styles
    if argv is None:
        return None
    m = re.match(r"([0-9]+)(GB|MB|KB|B)", argv)
    if not m:
        raise TypeError
    size, unit = m.groups()
    size = float(size)
    if unit == "KB":
        size *= 1024
    elif unit == "MB":
        size *= 1024**2
    elif unit == "GB":
        size *= 1024**3
    elif unit == "B":
        pass
    return size


def main() -> None:
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter
    )
    parser.add_argument(
        "-V",
        "--version",
        action=_ShowVersionAction,
        help="display version",
        nargs=0,
    )
    parser.add_argument("url_or_id", help="url or file/folder id to download from")
    parser.add_argument(
        "-O",
        "--output",
        help=(
            f'output file name/path; end with "{os.path.sep}" to create a new directory'
        ),
    )
    parser.add_argument(
        "-q",
        "--quiet",
        action="store_true",
        help="suppress logging except errors",
    )
    parser.add_argument(
        "--proxy",
        help="<protocol://host:port> download using the specified proxy",
    )
    parser.add_argument(
        "--speed",
        type=file_size,
        help="download speed limit in second (e.g., '10MB' -> 10MB/s)",
    )
    parser.add_argument(
        "--no-cookies",
        action="store_true",
        help="don't use cookies in ~/.cache/gdown/cookies.txt",
    )
    parser.add_argument(
        "--no-check-certificate",
        action="store_true",
        help="don't check the server's TLS certificate",
    )
    parser.add_argument(
        "--continue",
        "-c",
        dest="continue_",
        action="store_true",
        help="resume getting partially-downloaded files while "
        "skipping fully downloaded ones",
    )
    parser.add_argument(
        "--folder",
        action="store_true",
        help="download entire folder instead of a single file",
    )
    parser.add_argument(
        "--json",
        action="store_true",
        help=(
            "(beta) list file or folder contents as a JSON array on stdout "
            "instead of downloading. Each entry is an object with 'url' and "
            "'path'. The output format may change in a future release. "
            "Cannot be combined with -O/--output."
        ),
    )
    parser.add_argument(
        "--format",
        help="Format of Google Docs, Spreadsheets and Slides. "
        "Default is Google Docs: 'docx', Spreadsheet: 'xlsx', Slides: 'pptx'.",
    )
    parser.add_argument(
        "--user-agent",
        help="User-Agent to use for downloading file.",
    )

    args = parser.parse_args()

    if args.json and args.output is not None:
        parser.error("--json cannot be combined with -O/--output")

    if args.json and not args.quiet:
        print(
            "warning: `--json` is in beta and its output format may change in a "
            "future release",
            file=sys.stderr,
        )

    if args.output == "-":
        args.output = sys.stdout.buffer

    if re.match("^https?://.*", args.url_or_id):
        url = args.url_or_id
        id = None
    else:
        url = None
        id = args.url_or_id

    try:
        if args.folder:
            if not (args.output is None or isinstance(args.output, str)):
                raise ValueError("--folder does not support stdout output (-O -)")
            result = download_folder(
                url=url,
                id=id,
                output=args.output,
                quiet=args.quiet or args.json,
                proxy=args.proxy,
                speed=args.speed,
                use_cookies=not args.no_cookies,
                verify=not args.no_check_certificate,
                user_agent=args.user_agent,
                resume=args.continue_,
                skip_download=args.json,
            )
        else:
            result = download(
                url=url,
                output=args.output,
                quiet=args.quiet or args.json,
                proxy=args.proxy,
                speed=args.speed,
                use_cookies=not args.no_cookies,
                verify=not args.no_check_certificate,
                id=id,
                resume=args.continue_,
                format=args.format,
                user_agent=args.user_agent,
                skip_download=args.json,
            )

        if args.json:
            files = result if args.folder else [result]
            entries = []
            for file in files:
                assert isinstance(file, GoogleDriveFileToDownload)
                entries.append(
                    {
                        "url": f"https://drive.google.com/uc?id={file.id}",
                        "path": file.path.replace(os.sep, "/"),
                    }
                )
            print(json.dumps(entries, ensure_ascii=False, indent=2))
    except DownloadError as e:
        print(e, file=sys.stderr)
        sys.exit(1)
    except requests.exceptions.ProxyError as e:
        print(
            "Failed to use proxy:\n\n{}\n\nPlease check your proxy settings.".format(
                textwrap.indent("\n".join(textwrap.wrap(str(e))), prefix="\t")
            ),
            file=sys.stderr,
        )
        sys.exit(1)
    except Exception as e:
        print(
            "Error:\n\n{}\n\nTo report issues, please visit "
            "https://github.com/wkentaro/gdown/issues.".format(
                textwrap.indent("\n".join(textwrap.wrap(str(e))), prefix="\t")
            ),
            file=sys.stderr,
        )
        sys.exit(1)


if __name__ == "__main__":
    main()
