#!/usr/bin/python
# Copyright Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import annotations

DOCUMENTATION = r"""
module: pagerduty_alert
short_description: Trigger, acknowledge or resolve PagerDuty incidents
description:
  - This module lets you trigger, acknowledge or resolve a PagerDuty incident by sending events.
author:
  - "Amanpreet Singh (@ApsOps)"
  - "Xiao Shen (@xshen1)"
requirements:
  - PagerDuty API access
extends_documentation_fragment:
  - community.general._attributes
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  name:
    type: str
    description:
      - PagerDuty unique subdomain. Obsolete. It is not used with PagerDuty REST v2 API.
  api_key:
    type: str
    description:
      - The pagerduty API key (readonly access), generated on the pagerduty site.
      - Required if O(api_version=v1).
  integration_key:
    type: str
    description:
      - The GUID of one of your 'Generic API' services.
      - This is the 'integration key' listed on a 'Integrations' tab of PagerDuty service.
  service_id:
    type: str
    description:
      - ID of PagerDuty service when incidents are triggered, acknowledged or resolved.
      - Required if O(api_version=v1).
  service_key:
    type: str
    description:
      - The GUID of one of your 'Generic API' services. Obsolete. Please use O(integration_key).
  state:
    type: str
    description:
      - Type of event to be sent.
    required: true
    choices:
      - 'triggered'
      - 'acknowledged'
      - 'resolved'
  api_version:
    type: str
    description:
      - The API version we want to use to run the module.
      - V1 is more limited with option we can provide to trigger incident.
      - V2 has more variables for example, O(severity), O(source), O(custom_details) and so on.
    default: 'v1'
    choices:
      - 'v1'
      - 'v2'
    version_added: 7.4.0
  client:
    type: str
    description:
      - The name of the monitoring client that is triggering this event.
  client_url:
    type: str
    description:
      - The URL of the monitoring client that is triggering this event.
  component:
    type: str
    description:
      - Component of the source machine that is responsible for the event, for example C(mysql) or C(eth0).
    version_added: 7.4.0
  custom_details:
    type: dict
    description:
      - Additional details about the event and affected system.
      - A dictionary with custom keys and values.
    version_added: 7.4.0
  desc:
    type: str
    description:
      - For O(state=triggered) - Required. Short description of the problem that led to this trigger. This field (or a truncated
        version) is used when generating phone calls, SMS messages and alert emails. It also appears on the incidents tables
        in the PagerDuty UI. The maximum length is 1024 characters.
      - For O(state=acknowledged) or O(state=resolved) - Text that appears in the incident's log associated with this event.
    default: Created via Ansible
  incident_class:
    type: str
    description:
      - The class/type of the event, for example C(ping failure) or C(cpu load).
    version_added: 7.4.0
  incident_key:
    type: str
    description:
      - Identifies the incident to which this O(state) should be applied.
      - For O(state=triggered) - If there is no open (in other words unresolved) incident with this key, a new one is created.
        If there is already an open incident with a matching key, this event is appended to that incident's log. The event
        key provides an easy way to 'de-dup' problem reports. If no O(incident_key) is provided, then it is generated by PagerDuty.
      - For O(state=acknowledged) or O(state=resolved) - This should be the incident_key you received back when the incident
        was first opened by a trigger event. Acknowledge events referencing resolved or nonexistent incidents is discarded.
  link_url:
    type: str
    description:
      - Relevant link URL to the alert. For example, the website or the job link.
    version_added: 7.4.0
  link_text:
    type: str
    description:
      - A short description of the O(link_url).
    version_added: 7.4.0
  source:
    type: str
    description:
      - The unique location of the affected system, preferably a hostname or FQDN.
      - Required in case of O(state=trigger) and O(api_version=v2).
    version_added: 7.4.0
  severity:
    type: str
    description:
      - The perceived severity of the status the event is describing with respect to the affected system.
      - Required in case of O(state=trigger) and O(api_version=v2).
    default: 'critical'
    choices:
      - 'critical'
      - 'warning'
      - 'error'
      - 'info'
    version_added: 7.4.0
"""

EXAMPLES = r"""
- name: Trigger an incident with just the basic options
  community.general.pagerduty_alert:
    name: companyabc
    integration_key: xxx
    api_key: yourapikey
    service_id: PDservice
    state: triggered
    desc: problem that led to this trigger

- name: Trigger an incident with more options
  community.general.pagerduty_alert:
    integration_key: xxx
    api_key: yourapikey
    service_id: PDservice
    state: triggered
    desc: problem that led to this trigger
    incident_key: somekey
    client: Sample Monitoring Service
    client_url: http://service.example.com

- name: Acknowledge an incident based on incident_key
  community.general.pagerduty_alert:
    integration_key: xxx
    api_key: yourapikey
    service_id: PDservice
    state: acknowledged
    incident_key: somekey
    desc: "some text for incident's log"

- name: Resolve an incident based on incident_key
  community.general.pagerduty_alert:
    integration_key: xxx
    api_key: yourapikey
    service_id: PDservice
    state: resolved
    incident_key: somekey
    desc: "some text for incident's log"

- name: Trigger an v2 incident with just the basic options
  community.general.pagerduty_alert:
    integration_key: xxx
    api_version: v2
    source: My Ansible Script
    state: triggered
    desc: problem that led to this trigger

- name: Trigger an v2 incident with more options
  community.general.pagerduty_alert:
    integration_key: xxx
    api_version: v2
    source: My Ansible Script
    state: triggered
    desc: problem that led to this trigger
    incident_key: somekey
    client: Sample Monitoring Service
    client_url: http://service.example.com
    component: mysql
    incident_class: ping failure
    link_url: https://pagerduty.com
    link_text: PagerDuty

- name: Acknowledge an incident based on incident_key using v2
  community.general.pagerduty_alert:
    api_version: v2
    integration_key: xxx
    incident_key: somekey
    state: acknowledged

- name: Resolve an incident based on incident_key
  community.general.pagerduty_alert:
    api_version: v2
    integration_key: xxx
    incident_key: somekey
    state: resolved
"""
import json
from datetime import datetime
from urllib.parse import urlencode, urlparse, urlunparse

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.urls import fetch_url


def check(module, name, state, service_id, integration_key, api_key, incident_key=None, http_call=fetch_url):
    url = "https://api.pagerduty.com/incidents"
    headers = {
        "Content-type": "application/json",
        "Authorization": f"Token token={api_key}",
        "Accept": "application/vnd.pagerduty+json;version=2",
    }

    params = {"service_ids[]": service_id, "sort_by": "incident_number:desc", "time_zone": "UTC"}
    if incident_key:
        params["incident_key"] = incident_key

    url_parts = list(urlparse(url))
    url_parts[4] = urlencode(params, True)

    url = urlunparse(url_parts)

    response, info = http_call(module, url, method="get", headers=headers)

    if info["status"] != 200:
        module.fail_json(msg=f"failed to check current incident status.Reason: {info['msg']}")

    incidents = json.loads(response.read())["incidents"]
    msg = "No corresponding incident"

    if len(incidents) == 0:
        if state in ("acknowledged", "resolved"):
            return msg, False
        return msg, True
    elif state != incidents[0]["status"]:
        return incidents[0], True

    return incidents[0], False


def send_event_v1(module, service_key, event_type, desc, incident_key=None, client=None, client_url=None):
    url = "https://events.pagerduty.com/generic/2010-04-15/create_event.json"
    headers = {"Content-type": "application/json"}

    data = {
        "service_key": service_key,
        "event_type": event_type,
        "incident_key": incident_key,
        "description": desc,
        "client": client,
        "client_url": client_url,
    }

    response, info = fetch_url(module, url, method="post", headers=headers, data=json.dumps(data))
    if info["status"] != 200:
        module.fail_json(msg=f"failed to {event_type}. Reason: {info['msg']}")
    json_out = json.loads(response.read())
    return json_out


def send_event_v2(module, service_key, event_type, payload, link, incident_key=None, client=None, client_url=None):
    url = "https://events.pagerduty.com/v2/enqueue"
    headers = {"Content-type": "application/json"}
    data = {
        "routing_key": service_key,
        "event_action": event_type,
        "payload": payload,
        "client": client,
        "client_url": client_url,
    }
    if link:
        data["links"] = [link]
    if incident_key:
        data["dedup_key"] = incident_key
    if event_type != "trigger":
        data.pop("payload")
    response, info = fetch_url(module, url, method="post", headers=headers, data=json.dumps(data))
    if info["status"] != 202:
        module.fail_json(msg=f"failed to {event_type}. Reason: {info['msg']}")
    json_out = json.loads(response.read())
    return json_out, True


def main():
    module = AnsibleModule(
        argument_spec=dict(
            name=dict(),
            api_key=dict(no_log=True),
            integration_key=dict(no_log=True),
            service_id=dict(),
            service_key=dict(no_log=True),
            state=dict(required=True, choices=["triggered", "acknowledged", "resolved"]),
            api_version=dict(type="str", default="v1", choices=["v1", "v2"]),
            client=dict(),
            client_url=dict(),
            component=dict(),
            custom_details=dict(type="dict"),
            desc=dict(default="Created via Ansible"),
            incident_class=dict(),
            incident_key=dict(no_log=False),
            link_url=dict(),
            link_text=dict(),
            source=dict(),
            severity=dict(default="critical", choices=["critical", "warning", "error", "info"]),
        ),
        required_if=[
            ("api_version", "v1", ["service_id", "api_key"]),
            ("state", "acknowledged", ["incident_key"]),
            ("state", "resolved", ["incident_key"]),
        ],
        required_one_of=[("service_key", "integration_key")],
        supports_check_mode=True,
    )

    name = module.params["name"]
    service_id = module.params.get("service_id")
    integration_key = module.params.get("integration_key")
    service_key = module.params.get("service_key")
    api_key = module.params.get("api_key")
    state = module.params.get("state")
    client = module.params.get("client")
    client_url = module.params.get("client_url")
    desc = module.params.get("desc")
    incident_key = module.params.get("incident_key")
    payload = {
        "summary": desc,
        "source": module.params.get("source"),
        "timestamp": datetime.now().isoformat(),
        "severity": module.params.get("severity"),
        "component": module.params.get("component"),
        "class": module.params.get("incident_class"),
        "custom_details": module.params.get("custom_details"),
    }
    link = {}
    if module.params.get("link_url"):
        link["href"] = module.params.get("link_url")
        if module.params.get("link_text"):
            link["text"] = module.params.get("link_text")
    if integration_key is None:
        integration_key = service_key
        module.warn('"service_key" is obsolete parameter and will be removed. Please, use "integration_key" instead')

    state_event_dict = {
        "triggered": "trigger",
        "acknowledged": "acknowledge",
        "resolved": "resolve",
    }

    event_type = state_event_dict[state]
    if module.params.get("api_version") == "v1":
        out, changed = check(module, name, state, service_id, integration_key, api_key, incident_key)
        if not module.check_mode and changed is True:
            out = send_event_v1(module, integration_key, event_type, desc, incident_key, client, client_url)
    else:
        changed = True
        if event_type == "trigger" and not payload["source"]:
            module.fail_json(msg='"service" is a required variable for v2 api endpoint.')
        out, changed = send_event_v2(
            module,
            integration_key,
            event_type,
            payload,
            link,
            incident_key,
            client,
            client_url,
        )

    module.exit_json(result=out, changed=changed)


if __name__ == "__main__":
    main()
