#!/usr/bin/env python3

import datetime
import os
import random
import string
import sys
import time
import warnings
from typing import Any

import boto3
import requests


POLLING_DELAY_IN_SECOND = 5
MAX_UPLOAD_WAIT_IN_SECOND = 600

# NB: This is the curated top devices from AWS. We could create our own device
# pool if we want to
DEFAULT_DEVICE_POOL_ARN = (
    "arn:aws:devicefarm:us-west-2::devicepool:082d10e5-d7d7-48a5-ba5c-b33d66efa1f5"
)


def parse_args() -> Any:
    from argparse import ArgumentParser

    parser = ArgumentParser("Run iOS tests on AWS Device Farm")
    parser.add_argument(
        "--project-arn", type=str, required=True, help="the ARN of the project on AWS"
    )
    parser.add_argument(
        "--app-file", type=str, required=True, help="the iOS ipa app archive"
    )
    parser.add_argument(
        "--xctest-file", type=str, required=True, help="the XCTest suite to run"
    )
    parser.add_argument(
        "--name-prefix",
        type=str,
        required=True,
        help="the name prefix of this test run",
    )
    parser.add_argument(
        "--device-pool-arn",
        type=str,
        default=DEFAULT_DEVICE_POOL_ARN,
        help="the name of the device pool to test on",
    )

    return parser.parse_args()


def upload_file(
    client: Any,
    project_arn: str,
    prefix: str,
    filename: str,
    filetype: str,
    mime: str = "application/octet-stream",
):
    """
    Upload the app file and XCTest suite to AWS
    """
    r = client.create_upload(
        projectArn=project_arn,
        name=f"{prefix}_{os.path.basename(filename)}",
        type=filetype,
        contentType=mime,
    )
    upload_name = r["upload"]["name"]
    upload_arn = r["upload"]["arn"]
    upload_url = r["upload"]["url"]

    with open(filename, "rb") as file_stream:
        print(f"Uploading {filename} to Device Farm as {upload_name}...")
        r = requests.put(upload_url, data=file_stream, headers={"content-type": mime})
        if not r.ok:
            raise Exception(f"Couldn't upload {filename}: {r.reason}")  # noqa: TRY002

    start_time = datetime.datetime.now()
    # Polling AWS till the uploaded file is ready
    while True:
        waiting_time = datetime.datetime.now() - start_time
        if waiting_time > datetime.timedelta(seconds=MAX_UPLOAD_WAIT_IN_SECOND):
            raise Exception(  # noqa: TRY002
                f"Uploading {filename} is taking longer than {MAX_UPLOAD_WAIT_IN_SECOND} seconds, terminating..."
            )

        r = client.get_upload(arn=upload_arn)
        status = r["upload"].get("status", "")

        print(f"{filename} is in state {status} after {waiting_time}")

        if status == "FAILED":
            raise Exception(f"Couldn't upload {filename}: {r}")  # noqa: TRY002
        if status == "SUCCEEDED":
            break

        time.sleep(POLLING_DELAY_IN_SECOND)

    return upload_arn


def main() -> None:
    args = parse_args()

    client = boto3.client("devicefarm")
    unique_prefix = f"{args.name_prefix}-{datetime.date.today().isoformat()}-{''.join(random.sample(string.ascii_letters, 8))}"

    # Upload the test app
    appfile_arn = upload_file(
        client=client,
        project_arn=args.project_arn,
        prefix=unique_prefix,
        filename=args.app_file,
        filetype="IOS_APP",
    )
    print(f"Uploaded app: {appfile_arn}")
    # Upload the XCTest suite
    xctest_arn = upload_file(
        client=client,
        project_arn=args.project_arn,
        prefix=unique_prefix,
        filename=args.xctest_file,
        filetype="XCTEST_TEST_PACKAGE",
    )
    print(f"Uploaded XCTest: {xctest_arn}")

    # Schedule the test
    r = client.schedule_run(
        projectArn=args.project_arn,
        name=unique_prefix,
        appArn=appfile_arn,
        devicePoolArn=args.device_pool_arn,
        test={"type": "XCTEST", "testPackageArn": xctest_arn},
    )
    run_arn = r["run"]["arn"]

    start_time = datetime.datetime.now()
    print(f"Run {unique_prefix} is scheduled as {run_arn}:")

    state = "UNKNOWN"
    result = ""
    try:
        while True:
            r = client.get_run(arn=run_arn)
            state = r["run"]["status"]

            if state == "COMPLETED":
                result = r["run"]["result"]
                break

            waiting_time = datetime.datetime.now() - start_time
            print(
                f"Run {unique_prefix} in state {state} after {datetime.datetime.now() - start_time}"
            )
            time.sleep(30)
    except Exception as error:
        warnings.warn(f"Failed to run {unique_prefix}: {error}")
        sys.exit(1)

    if not result or result == "FAILED":
        print(f"Run {unique_prefix} failed, exiting...")
        sys.exit(1)


if __name__ == "__main__":
    main()
