#!/usr/bin/env python3
"""
FilePartage
Send any file to a phone/laptop/computer via the LocalSend protocol.

Author: Julien Perrissin-Fabert
Version: 20190217

Prerequisites:
    pip install requests
    
How to install requests:
    python -m ensurepip --default-pip
    python3 -m pip install requests
    
Usage:
    $ python3 FilePartage.py [Recipient's IP address] /path/to/my_archive.zip

Example:
    $ python3 FilePartage.py 192.168.1.42 my_archive.zip
"""

import argparse
import os
import uuid

import requests
import urllib3


# LocalSend uses a self-signed certificate -> we disable the warning.
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)


def send_file(
    server_host: str,
    file_path: str,
    port: int = 53317,
    alias: str = "FilePartage App",
    device_model: str = "macOS",
    device_type: str = "desktop",
    use_https: bool = True,
) -> None:
    """
    Sends a single file to the remote LocalSend server (v1 protocol).
    """
    if not os.path.isfile(file_path):
        raise FileNotFoundError(f"File not found: {file_path}")

    scheme = "https" if use_https else "http"
    base_url = f"{scheme}://{server_host}:{port}/api/localsend/v1"

    # File metadata (see v1.md: ‘files’ field indexed by fileId)
    file_id = str(uuid.uuid4())
    file_size = os.path.getsize(file_path)
    file_name = os.path.basename(file_path)

    # For a .zip file, you can set fileType="other"
    send_request_payload = {
        "info": {
            "alias": alias,
            "deviceModel": device_model,
            "deviceType": device_type,  # "mobile" | "desktop" | "web"
        },
        "files": {
            file_id: {
                "id": file_id,
                "fileName": file_name,
                "size": file_size,
                "fileType": "other",
                "preview": None,  # no thumbnail
            }
        },
    }

    # 1) Sending the metadata request
    print(f"[1/2] Sending the metadata request to {base_url}/send-request ...")
    resp = requests.post(
        f"{base_url}/send-request",
        json=send_request_payload,
        timeout=10,
        verify=False,  # self-signed certificate
    )
    resp.raise_for_status()
    tokens = resp.json()

    if file_id not in tokens:
        raise RuntimeError(
            f"The recipient likely rejected the file (id {file_id} not included in the response): {tokens}"
        )

    token = tokens[file_id]
    print(f"Token received for the file {file_name}: {token}")

    # 2) Sending binary content
    send_url = f"{base_url}/send"
    params = {"fileId": file_id, "token": token}
    print(f"[2/2] Sending binary data to {send_url} ...")

    with open(file_path, "rb") as f:
        resp = requests.post(
            send_url,
            params=params,
            data=f,
            timeout=None,  # allow enough time
            verify=False,
            headers={"Content-Type": "application/octet-stream"},
        )
    resp.raise_for_status()

    print("Transfer completed successfully.")


def main():
    parser = argparse.ArgumentParser(
        description="Send any file to a phone/laptop/computer via the LocalSend protocol."
    )
    parser.add_argument(
        "host",
        help="The phone's IP address or hostname (on the same local network, with LocalSend open).",
    )
    parser.add_argument(
        "file",
        help="Path to the .zip file to be sent.",
    )
    parser.add_argument(
        "--port",
        type=int,
        default=53317,
        help="The phone's LocalSend port (default: 53317).",
    )
    parser.add_argument(
        "--alias",
        default="MacBook",
        help="The name or alias of the transmitting device displayed on the phone.",
    )
    parser.add_argument(
        "--device-model",
        default="macOS",
        help="Transmitter model (optional information).",
    )
    parser.add_argument(
        "--device-type",
        default="desktop",
        choices=["mobile", "desktop", "web"],
        help="Transmitter device type (default: desktop).",
    )
    parser.add_argument(
        "--no-https",
        action="store_true",
        help="Use HTTP instead of HTTPS (not recommended).",
    )

    args = parser.parse_args()

    send_file(
        server_host=args.host,
        file_path=args.file,
        port=args.port,
        alias=args.alias,
        device_model=args.device_model,
        device_type=args.device_type,
        use_https=not args.no_https,
    )


if __name__ == "__main__":
    main()
