"""VoicePro Plus API v3 client for Python. Generated from the API contract; needs requests.

Docs: https://voicepro.plus/developers
Create a client with your key, then call a method, for example get_account or create_dip.
"""
import uuid
from urllib.parse import quote

import requests


class VoiceProError(Exception):
    def __init__(self, status, code, message, request_id=None):
        super().__init__(message)
        self.status, self.code, self.request_id = status, code, request_id


class VoicePro:
    def __init__(self, api_key, base_url="https://api.voicepro.plus/v3"):
        self.api_key, self.base_url = api_key, base_url

    def call(self, method, path, query=None, body=None, idempotency_key=None):
        "One request. A change carries a fresh Idempotency-Key unless you pass your own (reuse it to retry safely)."
        headers = {"Authorization": f"Bearer {self.api_key}"}
        if method != "GET":
            headers["Idempotency-Key"] = idempotency_key or str(uuid.uuid4())
        res = requests.request(method, self.base_url + path, params=query, json=body, headers=headers, timeout=30)
        data = None if res.status_code == 204 else res.json()
        if not res.ok:
            err = (data or {}).get("error", {})
            raise VoiceProError(res.status_code, err.get("code", "unknown"), err.get("message", res.reason), err.get("request_id"))
        return data

    def get_account(self, query=None, body=None, idempotency_key=None):
        "The workspace this key belongs to, the services on it and the key's own scopes."
        return self.call("GET", f"/account", query=query, body=body, idempotency_key=idempotency_key)

    def list_sub_accounts(self, query=None, body=None, idempotency_key=None):
        "Sub-accounts, each with its counts of SIMs, numbers and active members, and whether it has a voice trunk."
        return self.call("GET", f"/sub_accounts", query=query, body=body, idempotency_key=idempotency_key)

    def list_incidents(self, query=None, body=None, idempotency_key=None):
        "Open incidents and planned maintenance (next 3 days) affecting this account's services."
        return self.call("GET", f"/incidents", query=query, body=body, idempotency_key=idempotency_key)

    def list_sims(self, query=None, body=None, idempotency_key=None):
        "Mobile SIMs with this month's usage."
        return self.call("GET", f"/sims", query=query, body=body, idempotency_key=idempotency_key)

    def create_sim(self, query=None, body=None, idempotency_key=None):
        "Add a business mobile SIM line."
        return self.call("POST", f"/sims", query=query, body=body, idempotency_key=idempotency_key)

    def get_sim(self, id, query=None, body=None, idempotency_key=None):
        "One mobile SIM."
        return self.call("GET", f"/sims/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def get_sim_usage(self, id, query=None, body=None, idempotency_key=None):
        "Usage for a time range (at most 400 days)."
        return self.call("GET", f"/sims/{quote(str(id), safe='')}/usage", query=query, body=body, idempotency_key=idempotency_key)

    def list_iot_sims(self, query=None, body=None, idempotency_key=None):
        "IoT SIMs. 404 service_not_enabled without IoT."
        return self.call("GET", f"/iot/sims", query=query, body=body, idempotency_key=idempotency_key)

    def get_iot_sim(self, id, query=None, body=None, idempotency_key=None):
        "One IoT SIM with this month's data."
        return self.call("GET", f"/iot/sims/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def update_iot_sim(self, id, query=None, body=None, idempotency_key=None):
        "Change one SIM: its name, on or off, IMEI lock, data limit or radio technologies. Checked in full before anything is sent; the answer is the SIM afterwards."
        return self.call("PATCH", f"/iot/sims/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def list_iot_sim_events(self, id, query=None, body=None, idempotency_key=None):
        "The network log of one IoT SIM over a time range, newest first: authentication results (allowed or refused, with the network's reason), attaches, data sessions starting, detaches with the session's bytes, and location updates. Default range: the last 7 days; at most 31 days. truncated is true when the range held more than 5,000 network rows (narrow the range). Reads are limited to 10 a minute per account."
        return self.call("GET", f"/iot/sims/{quote(str(id), safe='')}/events", query=query, body=body, idempotency_key=idempotency_key)

    def set_iot_sim_connector(self, id, query=None, body=None, idempotency_key=None):
        "Send one IoT SIM's traffic through one of your connectors, or through none (connector null)."
        return self.call("PATCH", f"/iot/sims/{quote(str(id), safe='')}/connector", query=query, body=body, idempotency_key=idempotency_key)

    def list_iot_connectors(self, query=None, body=None, idempotency_key=None):
        "The account's IoT cloud connectors, and the connector types on offer (types)."
        return self.call("GET", f"/iot/connectors", query=query, body=body, idempotency_key=idempotency_key)

    def create_iot_connector(self, query=None, body=None, idempotency_key=None):
        "Create a cloud connector: device traffic delivered to your HTTPS endpoint, TLS server or Azure IoT Hub, with TLS added in the network."
        return self.call("POST", f"/iot/connectors", query=query, body=body, idempotency_key=idempotency_key)

    def get_iot_connector(self, name, query=None, body=None, idempotency_key=None):
        "One cloud connector."
        return self.call("GET", f"/iot/connectors/{quote(str(name), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def update_iot_connector(self, name, query=None, body=None, idempotency_key=None):
        "Change a connector's settings, replace a secret or set its passthrough rules. A secret left out is kept. The type and name cannot change."
        return self.call("PATCH", f"/iot/connectors/{quote(str(name), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def delete_iot_connector(self, name, query=None, body=None, idempotency_key=None):
        "Delete a connector. Refused (409 state_conflict) while any SIM still uses it."
        return self.call("DELETE", f"/iot/connectors/{quote(str(name), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def test_iot_connector(self, name, query=None, body=None, idempotency_key=None):
        "Test a connector: an HTTPS connector gets a labelled test POST from our platform; every type shows its recent delivery errors from the network."
        return self.call("POST", f"/iot/connectors/{quote(str(name), safe='')}/test", query=query, body=body, idempotency_key=idempotency_key)

    def list_numbers(self, query=None, body=None, idempotency_key=None):
        "Phone numbers and their routing."
        return self.call("GET", f"/numbers", query=query, body=body, idempotency_key=idempotency_key)

    def get_number(self, id, query=None, body=None, idempotency_key=None):
        "One number."
        return self.call("GET", f"/numbers/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def update_number_routing(self, id, query=None, body=None, idempotency_key=None):
        "Change a number's routing: ring the app, or forward to another number."
        return self.call("PATCH", f"/numbers/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def get_number_usage(self, id, query=None, body=None, idempotency_key=None):
        "Calls and SMS for a time range."
        return self.call("GET", f"/numbers/{quote(str(id), safe='')}/usage", query=query, body=body, idempotency_key=idempotency_key)

    def list_number_offers(self, query=None, body=None, idempotency_key=None):
        "Numbers you can order in a country, with their prepaid price in the currency you choose."
        return self.call("GET", f"/number_offers", query=query, body=body, idempotency_key=idempotency_key)

    def create_number_order(self, query=None, body=None, idempotency_key=None):
        "Order a number. Prepaid from your account balance."
        return self.call("POST", f"/number_orders", query=query, body=body, idempotency_key=idempotency_key)

    def list_port_requests(self, query=None, body=None, idempotency_key=None):
        "Requests to transfer numbers in."
        return self.call("GET", f"/port_requests", query=query, body=body, idempotency_key=idempotency_key)

    def create_port_request(self, query=None, body=None, idempotency_key=None):
        "Ask to transfer numbers to VoicePro from another provider."
        return self.call("POST", f"/port_requests", query=query, body=body, idempotency_key=idempotency_key)

    def get_port_request(self, id, query=None, body=None, idempotency_key=None):
        "One transfer request with its history."
        return self.call("GET", f"/port_requests/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def list_trunks(self, query=None, body=None, idempotency_key=None):
        "Trunk settings. 404 service_not_enabled without a voice trunk."
        return self.call("GET", f"/trunks", query=query, body=body, idempotency_key=idempotency_key)

    def list_rates(self, query=None, body=None, idempotency_key=None):
        "Current and scheduled rates."
        return self.call("GET", f"/rates", query=query, body=body, idempotency_key=idempotency_key)

    def list_calls(self, query=None, body=None, idempotency_key=None):
        "Call records, newest first, for a range of at most 92 days (default: the last 24 hours)."
        return self.call("GET", f"/calls", query=query, body=body, idempotency_key=idempotency_key)

    def get_call(self, id, query=None, body=None, idempotency_key=None):
        "One call record by id, within the last 92 days."
        return self.call("GET", f"/calls/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def get_traffic(self, query=None, body=None, idempotency_key=None):
        "Totals and series for a time range."
        return self.call("GET", f"/traffic", query=query, body=body, idempotency_key=idempotency_key)

    def list_traffic_destinations(self, query=None, body=None, idempotency_key=None):
        "Traffic by destination country for a time range."
        return self.call("GET", f"/traffic/destinations", query=query, body=body, idempotency_key=idempotency_key)

    def list_activity(self, query=None, body=None, idempotency_key=None):
        "Calls, SMS and data across SIMs, numbers and the voice trunk, newest first. Each source gives at most 5000 records per window; has_more then pages into older records. Voice trunk records cover at most the last 92 days."
        return self.call("GET", f"/activity", query=query, body=body, idempotency_key=idempotency_key)

    def list_invoices(self, query=None, body=None, idempotency_key=None):
        "VoicePro invoices, newest first."
        return self.call("GET", f"/invoices", query=query, body=body, idempotency_key=idempotency_key)

    def get_invoice(self, id, query=None, body=None, idempotency_key=None):
        "One invoice with its lines."
        return self.call("GET", f"/invoices/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def get_invoice_pdf(self, id, query=None, body=None, idempotency_key=None):
        "The invoice as a VAT invoice PDF."
        return self.call("GET", f"/invoices/{quote(str(id), safe='')}/pdf", query=query, body=body, idempotency_key=idempotency_key)

    def list_wholesale_invoices(self, query=None, body=None, idempotency_key=None):
        "Wholesale voice invoices."
        return self.call("GET", f"/wholesale/invoices", query=query, body=body, idempotency_key=idempotency_key)

    def list_wholesale_payments(self, query=None, body=None, idempotency_key=None):
        "Payments received for wholesale voice."
        return self.call("GET", f"/wholesale/payments", query=query, body=body, idempotency_key=idempotency_key)

    def get_balance(self, query=None, body=None, idempotency_key=None):
        "Wholesale balance and prepaid dip balance."
        return self.call("GET", f"/balance", query=query, body=body, idempotency_key=idempotency_key)

    def get_spend(self, query=None, body=None, idempotency_key=None):
        "This month's spend by service, the projection and the spending limit."
        return self.call("GET", f"/spend", query=query, body=body, idempotency_key=idempotency_key)

    def list_dip_products(self, query=None, body=None, idempotency_key=None):
        "Dip products you can buy, with live prices."
        return self.call("GET", f"/dips/products", query=query, body=body, idempotency_key=idempotency_key)

    def list_dips(self, query=None, body=None, idempotency_key=None):
        "Past single dips, newest first: the most recent 200, each kept for 90 days."
        return self.call("GET", f"/dips", query=query, body=body, idempotency_key=idempotency_key)

    def create_dip(self, query=None, body=None, idempotency_key=None):
        "Dip one number: the chosen products, answered now."
        return self.call("POST", f"/dips", query=query, body=body, idempotency_key=idempotency_key)

    def get_dip(self, id, query=None, body=None, idempotency_key=None):
        "One past dip."
        return self.call("GET", f"/dips/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def get_dip_usage(self, query=None, body=None, idempotency_key=None):
        "Dips this month by product."
        return self.call("GET", f"/dips/usage", query=query, body=body, idempotency_key=idempotency_key)

    def list_dip_batches(self, query=None, body=None, idempotency_key=None):
        "Dip batches, newest first."
        return self.call("GET", f"/dip_batches", query=query, body=body, idempotency_key=idempotency_key)

    def create_dip_batch(self, query=None, body=None, idempotency_key=None):
        "Check a file of up to 20,000 numbers in the background."
        return self.call("POST", f"/dip_batches", query=query, body=body, idempotency_key=idempotency_key)

    def get_dip_batch(self, id, query=None, body=None, idempotency_key=None):
        "One batch with its progress."
        return self.call("GET", f"/dip_batches/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def list_dip_batch_results(self, id, query=None, body=None, idempotency_key=None):
        "Batch results; 409 state_conflict while running unless partial=true. CSV with Accept: text/csv."
        return self.call("GET", f"/dip_batches/{quote(str(id), safe='')}/results", query=query, body=body, idempotency_key=idempotency_key)

    def create_iot_bulk_job(self, query=None, body=None, idempotency_key=None):
        "One change for up to 5,000 of the account's IoT SIMs, run in the background."
        return self.call("POST", f"/iot/bulk_jobs", query=query, body=body, idempotency_key=idempotency_key)

    def get_iot_bulk_job(self, id, query=None, body=None, idempotency_key=None):
        "One bulk job with its progress."
        return self.call("GET", f"/iot/bulk_jobs/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def list_iot_bulk_job_results(self, id, query=None, body=None, idempotency_key=None):
        "Each SIM's result, in the order asked: ok, failed with a reason, or pending. CSV with Accept: text/csv."
        return self.call("GET", f"/iot/bulk_jobs/{quote(str(id), safe='')}/results", query=query, body=body, idempotency_key=idempotency_key)

    def get_trunk_channels(self, query=None, body=None, idempotency_key=None):
        "Channels in use per trunk, now and at peak, against each trunk's limit, with a series."
        return self.call("GET", f"/trunks/channels", query=query, body=body, idempotency_key=idempotency_key)

    def get_fraud_incidents(self, query=None, body=None, idempotency_key=None):
        "Fraud incidents and losses our fraud engine raised on this account's traffic, against the previous period."
        return self.call("GET", f"/fraud/incidents", query=query, body=body, idempotency_key=idempotency_key)

    def list_risk_ranges(self, query=None, body=None, idempotency_key=None):
        "Search the high-risk and premium-rate number ranges our network screens against. total and truncated say when a search matched more than one answer holds (500)."
        return self.call("GET", f"/risk_ranges", query=query, body=body, idempotency_key=idempotency_key)

    def list_number_requirements(self, query=None, body=None, idempotency_key=None):
        "What you must supply, per country and number type, before a number goes live: personal, business and address documents, and notes. Leave out country for every country."
        return self.call("GET", f"/number_requirements", query=query, body=body, idempotency_key=idempotency_key)

    def list_webhook_events(self, query=None, body=None, idempotency_key=None):
        "The events you can subscribe a webhook to."
        return self.call("GET", f"/webhook_events", query=query, body=body, idempotency_key=idempotency_key)

    def list_webhooks(self, query=None, body=None, idempotency_key=None):
        "Your account's webhooks, newest first. The signing secret is never shown here, only its last 4 characters."
        return self.call("GET", f"/webhooks", query=query, body=body, idempotency_key=idempotency_key)

    def create_webhook(self, query=None, body=None, idempotency_key=None):
        "Subscribe an HTTPS endpoint to an event. The answer carries the signing secret, the only time it is shown: keep it to check X-VoicePro-Signature (HMAC-SHA256 of the raw body)."
        return self.call("POST", f"/webhooks", query=query, body=body, idempotency_key=idempotency_key)

    def get_webhook(self, id, query=None, body=None, idempotency_key=None):
        "One webhook."
        return self.call("GET", f"/webhooks/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def update_webhook(self, id, query=None, body=None, idempotency_key=None):
        "Change the URL, or pause and resume deliveries. A refused URL changes nothing."
        return self.call("PATCH", f"/webhooks/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def delete_webhook(self, id, query=None, body=None, idempotency_key=None):
        "Delete a webhook. Deliveries stop at once."
        return self.call("DELETE", f"/webhooks/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def test_webhook(self, id, query=None, body=None, idempotency_key=None):
        "Send a signed test delivery (type 'test', header X-VoicePro-Test: true) and see how your endpoint answered. At most 10 an hour per webhook and 30 per account."
        return self.call("POST", f"/webhooks/{quote(str(id), safe='')}/test", query=query, body=body, idempotency_key=idempotency_key)

    def list_webhook_deliveries(self, id, query=None, body=None, idempotency_key=None):
        "The last 50 deliveries to a webhook, newest first, with how your endpoint answered."
        return self.call("GET", f"/webhooks/{quote(str(id), safe='')}/deliveries", query=query, body=body, idempotency_key=idempotency_key)

    def get_iot_sim_esim(self, id, query=None, body=None, idempotency_key=None):
        "The SIM's eSIM profile state (for example RELEASED, DOWNLOADED, ENABLED); null for a physical SIM."
        return self.call("GET", f"/iot/sims/{quote(str(id), safe='')}/esim", query=query, body=body, idempotency_key=idempotency_key)

    def list_iot_sim_sms(self, id, query=None, body=None, idempotency_key=None):
        "The SIM's last 100 SMS, newest first: time, direction and status. Message text is not kept here; it arrives by the iot.sms webhook."
        return self.call("GET", f"/iot/sims/{quote(str(id), safe='')}/sms", query=query, body=body, idempotency_key=idempotency_key)

    def send_iot_sim_sms(self, id, query=None, body=None, idempotency_key=None):
        "Send a text SMS to the device on an IoT SIM (configuration, wake-up)."
        return self.call("POST", f"/iot/sims/{quote(str(id), safe='')}/sms", query=query, body=body, idempotency_key=idempotency_key)

    def set_iot_sim_network_list(self, id, query=None, body=None, idempotency_key=None):
        "Restrict a SIM to one of your account's network lists, or lift the restriction (name null)."
        return self.call("PATCH", f"/iot/sims/{quote(str(id), safe='')}/network_list", query=query, body=body, idempotency_key=idempotency_key)

    def list_iot_network_lists(self, query=None, body=None, idempotency_key=None):
        "Your account's network lists: the mobile networks (MCC and MNC) a SIM on the list may use. Lists are set up with our IoT team."
        return self.call("GET", f"/iot/network_lists", query=query, body=body, idempotency_key=idempotency_key)

    def cancel_number(self, id, query=None, body=None, idempotency_key=None):
        "Cancel a number: no further charges; it keeps working to the end of the month already paid, then is released."
        return self.call("POST", f"/numbers/{quote(str(id), safe='')}/cancel", query=query, body=body, idempotency_key=idempotency_key)

    def list_number_watches(self, query=None, body=None, idempotency_key=None):
        "The account's number watches, newest first."
        return self.call("GET", f"/number_watches", query=query, body=body, idempotency_key=idempotency_key)

    def create_number_watch(self, query=None, body=None, idempotency_key=None):
        "Watch a number: re-checked daily, with every change recorded and sent to your webhooks."
        return self.call("POST", f"/number_watches", query=query, body=body, idempotency_key=idempotency_key)

    def get_number_watch(self, id, query=None, body=None, idempotency_key=None):
        "One number watch, with the number's answer at the last check."
        return self.call("GET", f"/number_watches/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def delete_number_watch(self, id, query=None, body=None, idempotency_key=None):
        "Stop watching a number. Its change history goes with it."
        return self.call("DELETE", f"/number_watches/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def list_number_watch_events(self, id, query=None, body=None, idempotency_key=None):
        "A watch's changes, newest first (the last 50)."
        return self.call("GET", f"/number_watches/{quote(str(id), safe='')}/events", query=query, body=body, idempotency_key=idempotency_key)

    def suspend_sim(self, id, query=None, body=None, idempotency_key=None):
        "Suspend a SIM on the network: no calls, texts or data until it is reactivated."
        return self.call("POST", f"/sims/{quote(str(id), safe='')}/suspend", query=query, body=body, idempotency_key=idempotency_key)

    def reactivate_sim(self, id, query=None, body=None, idempotency_key=None):
        "Reactivate a suspended SIM."
        return self.call("POST", f"/sims/{quote(str(id), safe='')}/reactivate", query=query, body=body, idempotency_key=idempotency_key)

    def create_invoice_payment_link(self, id, query=None, body=None, idempotency_key=None):
        "A secure payment link for an open invoice, to pay it or send it on."
        return self.call("POST", f"/invoices/{quote(str(id), safe='')}/payment_link", query=query, body=body, idempotency_key=idempotency_key)

    def list_events(self, query=None, body=None, idempotency_key=None):
        "Your account's events from the last 30 days, newest first, as sent to your webhooks."
        return self.call("GET", f"/events", query=query, body=body, idempotency_key=idempotency_key)

    def get_event(self, id, query=None, body=None, idempotency_key=None):
        "One event from the last 30 days."
        return self.call("GET", f"/events/{quote(str(id), safe='')}", query=query, body=body, idempotency_key=idempotency_key)

    def resend_event(self, id, query=None, body=None, idempotency_key=None):
        "Send an event again: to every webhook that receives its type, or to one."
        return self.call("POST", f"/events/{quote(str(id), safe='')}/resend", query=query, body=body, idempotency_key=idempotency_key)
