#!/usr/bin/env python
# -*- coding: utf-8 -*-
# File:                Ampel-core/ampel/ops/AmpelExceptionPublisher.py
# License:             BSD-3-Clause
# Author:              Jakob van Santen <jakob.van.santen@desy.de>
# Date:                03.09.2018
# Last Modified Date:  27.08.2020
# Last Modified By:    Jakob van Santen <jakob.van.santen@desy.de>


import datetime
import json
import socket
from typing import Any

from bson import ObjectId
from slack_sdk.web import WebClient
from slack_sdk.web.slack_response import SlackResponse

from ampel.abstract.AbsOpsUnit import AbsOpsUnit
from ampel.secret.NamedSecret import NamedSecret


class AmpelExceptionPublisher(AbsOpsUnit):

    slack_token: NamedSecret[str] = NamedSecret[str](label="slack/operator")
    user: str = f"ampel@{socket.gethostname()}"
    channel: str = "ampel-troubles"
    dry_run: bool = False
    quiet: bool = True

    def __init__(self, **kwargs) -> None:
        super().__init__(**kwargs)
        self.slack = WebClient(self.slack_token.get())
        self.troubles = self.context.db.get_collection("trouble", "r")

    def t3_fields(self, doc: dict[str, Any]) -> list[dict[str, Any]]:
        fields = []
        if "job" in doc:
            fields.append(
                {"title": "Job", "value": doc.get("job"), "short": True}
            )
        if "task" in doc:
            fields.append(
                {"title": "Task", "value": doc.get("task"), "short": True}
            )
        fields.append({"title": "Run", "value": doc.get("run"), "short": True})
        return fields

    def format_attachment(self, doc: dict[str, Any]) -> dict[str, Any]:
        fields = [
            {"title": "tier", "value": doc["tier"], "short": True},
        ]
        if "process" in doc:
            fields.append({"title": "process", "value": doc["process"], "short": True})
        if "run" in doc:
            fields.append({"title": "run", "value": doc["run"], "short": True})
        if doc["tier"] == 0:
            for field in "section", "stock":
                fields.append(  # noqa: PERF401
                    {"title": field, "value": doc.get(field), "short": True}
                )
            if "alert" in doc:
                fields.append(
                    {"title": "alert", "value": doc["alert"], "short": True}
                )
        elif doc["tier"] == 2:
            fields.append(
                {"title": "unit", "value": doc.get("unit"), "short": True}
            )
            t2Doc = doc.get("t2Doc")
            if t2Doc and hasattr(t2Doc, "binary"):
                fields.append(
                    {"title": "t2Doc", "value": t2Doc.binary.hex(), "short": True}
                )
        if "exception" in doc:
            text = "```{}```".format("\n".join(doc["exception"]))
        elif "location" in doc:
            text = "{}: {}".format(doc["location"], doc.get("ampelMsg", ""))
            if "mongoUpdateResult" in doc:
                text += "\nmongoUpdateResult: `{}`".format(doc["mongoUpdateResult"])
            elif "errDict" in doc:
                text += "```\n{}```".format(repr(doc["errDict"]))
        else:
            text = f"Unknown exception type. Doc keys are: ```{doc.keys()}```"

        return {
            "fields": fields,
            "ts": int(doc["_id"].generation_time.timestamp()),
            "text": text,
            "mrkdwn_in": ["text"],
        }

    def run(self, beacon: None | dict[str, Any] = None) -> None | dict[str, Any]:

        now = datetime.datetime.now(tz=datetime.timezone.utc)
        t0 = beacon["updated"] if beacon else now - datetime.timedelta(hours=1)
        dt = now - t0

        attachments: list[dict[str, Any]] = []
        message = {
            "attachments": attachments,
            "channel": "#" + self.channel,
            "username": self.user,
            "as_user": False,
        }

        docs = list(self.troubles.find({"_id": {"$gt": ObjectId.from_datetime(t0)}}))
        for doc in docs:
            if len(attachments) < 20:
                attachments.append(self.format_attachment(doc))
            else:
                break

        if dt.days > 3:
            time_range = f"{dt.days} days"
        elif dt.days > 0 or dt.seconds > 2 * 3600:
            time_range = f"{int(dt.days * 24 + dt.seconds / 3600)} hours"
        elif dt.seconds > 2 * 60:
            time_range = f"{int(dt.seconds / 60)} minutes"
        else:
            time_range = f"{int(dt.seconds)} seconds"

        count = len(docs)
        if len(attachments) < count:
            message[
                "text"
            ] = f"Here are the first {len(attachments)} exceptions. There were {count-len(attachments)} more in the last {time_range}."
        else:
            message[
                "text"
            ] = f"There were {len(attachments)} exceptions in the last {time_range}."

        if self.dry_run:
            self.logger.info(json.dumps(message, indent=1))
        elif attachments or not self.quiet:
            result = self.slack.api_call("chat.postMessage", json=message)
            if isinstance(result, SlackResponse):
                if not result["ok"]:
                    raise RuntimeError(result["error"])
            else:
                raise TypeError(f"Sync client returned a future {result}")
        self.logger.info(f"{count} exceptions in the last {time_range}".format(count))

        return {"updated": now}
