#!/bin/env python

import requests

import pprint
import argparse
import logging
import os
import hashlib
from pathlib import Path

logging.basicConfig(level=logging.INFO)
_logger = logging.getLogger("netlify_file_digest_deploy")

API_URL = "https://api.netlify.com/api/v1/"

def request_with_auth(method, url, auth_header, **kwargs):
    kwargs["headers"] = dict(kwargs.get("headers", dict()), **auth_header)
    r = requests.request(method=method, url=url, **kwargs)
    if not r.ok:
        _logger.error(f"{method} to {url} failed {r.status_code}")
        _logger.error(r.text)
        return None
    return r.json()

def fname2absfname(fname):
    return fname[fname.index("/"):]

def netlify_file_digest_deploy(token, site_id, rootdir, dry_run=True):
    auth_header = {"Authorization": f"Bearer {token}" }
    # remove leading ./ and trailing /
    rootdir = Path(rootdir).as_posix()
    filenames = []
    for dirpath, dirnames, basenames in os.walk(rootdir):
        for basename in basenames:
            filenames.append(os.path.join(dirpath, basename))

    filename2hash = dict()
    hash2filename = dict()

    for filename in filenames:
        for c in "?#":
            if c in filename:
                _logger.error(f"unsupported character {c} in filename {filename}")
                return
        with open(filename, "rb") as f:
            filename2hash[filename] = hashlib.sha1(f.read()).hexdigest()
            hash2filename[filename2hash[filename]] = filename

    abs_filename2hash = {
        fname2absfname(fname): hash for fname, hash in filename2hash.items()
    }

    url = f"{API_URL}sites/{site_id}/deploys"
    r = request_with_auth(
        "POST", url, auth_header=auth_header, json=dict(files=abs_filename2hash)
    )
    if r is None:
        return

    deploy_id = r["id"]
    required_files = [hash2filename[h] for h in r["required"]]

    _logger.info(f"deploy_id {deploy_id} requires {len(required_files)} files:")
    _logger.info(pprint.pformat(required_files))

    if dry_run:
        _logger.info(f"cancelling deploy {deploy_id} and exiting dry-run")
        cancel_deploy(deploy_id, auth_header)
        return

    try:
        for i, filename in enumerate(required_files):
            _logger.info(f"{str(i+1).rjust(len(required_files))}/{len(required_files)}: uploading {filename}")
            url = f"{API_URL}deploys/{deploy_id}/files{fname2absfname(filename)}"
            r = request_with_auth(
                "PUT", url, auth_header, 
                headers={"Content-Type": "application/octet-stream"},
                data=open(filename, "rb")
            )
    except Exception as e:
        _logger.exception(e)
        _logger.error(f"encountered exception, cancelling deploy {deploy_id} and exiting")
        cancel_deploy(deploy_id, auth_header)

def cancel_deploy(deploy_id, auth_header):
    url = f"{API_URL}deploys/{deploy_id}/cancel"
    r = request_with_auth("POST", url, auth_header=auth_header)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        prog="netlify_file_digest_deploy",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description="""
Deploys a netlify site using the file digest API. Requires an already-existing site.

Generate a personal access token here: https://app.netlify.com/user/applications/personal

Docs: https://docs.netlify.com/api-and-cli-guides/api-guides/get-started-with-api/
Archive: https://web.archive.org/web/20260130225914/https://docs.netlify.com/api-and-cli-guides/api-guides/get-started-with-api/
        """,
    )
    parser.add_argument(
        "--dry-run", 
        action="store_true", 
        help=(
            "print a list of files that will be uploaded and exit without "
            "uploading. Note: this will create a new deploy and then cancel it."
        ),
        required=False
    )
    parser.add_argument("--token", help="netlify personal access token")
    parser.add_argument("--site-id", help="netlify site id")
    parser.add_argument("rootdir", help="the root directory of the site")
    args = parser.parse_args()
    netlify_file_digest_deploy(
        token=args.token, site_id=args.site_id,
        rootdir=args.rootdir, dry_run=args.dry_run
    )
