// Copyright 2022 Prometheus Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package telegram

import (
	"context"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"strconv"
	"strings"

	commoncfg "github.com/prometheus/common/config"
	"gopkg.in/telebot.v3"

	"github.com/prometheus/alertmanager/config"
	"github.com/prometheus/alertmanager/notify"
	"github.com/prometheus/alertmanager/template"
	"github.com/prometheus/alertmanager/types"
)

// Telegram supports 4096 chars max - from https://limits.tginfo.me/en.
const maxMessageLenRunes = 4096

// Notifier implements a Notifier for telegram notifications.
type Notifier struct {
	conf    *config.TelegramConfig
	tmpl    *template.Template
	logger  *slog.Logger
	client  *telebot.Bot
	retrier *notify.Retrier
}

// New returns a new Telegram notification handler.
func New(conf *config.TelegramConfig, t *template.Template, l *slog.Logger, httpOpts ...commoncfg.HTTPClientOption) (*Notifier, error) {
	httpclient, err := notify.NewClientWithTracing(*conf.HTTPConfig, "telegram", httpOpts...)
	if err != nil {
		return nil, err
	}

	client, err := createTelegramClient(conf.APIUrl.String(), conf.ParseMode, httpclient)
	if err != nil {
		return nil, err
	}

	return &Notifier{
		conf:    conf,
		tmpl:    t,
		logger:  l,
		client:  client,
		retrier: &notify.Retrier{},
	}, nil
}

func (n *Notifier) Notify(ctx context.Context, alert ...*types.Alert) (bool, error) {
	key, ok := notify.GroupKey(ctx)
	if !ok {
		return false, fmt.Errorf("group key missing")
	}

	logger := n.logger.With("group_key", key)
	logger.Debug("extracted group key")

	var (
		err         error
		data        = notify.GetTemplateData(ctx, n.tmpl, alert, logger)
		tmpl        = notify.TmplText(n.tmpl, data, &err)
		messageText string
		truncated   bool
	)

	switch n.conf.ParseMode {
	case "HTML":
		tmpl = notify.TmplHTML(n.tmpl, data, &err)
		messageText = tmpl(n.conf.Message)
		if err != nil {
			return false, err
		}
		if len([]rune(messageText)) > maxMessageLenRunes {
			messageText = `Alertmanager notification could not be sent: message length exceeds Telegram limits.
			Please check the template used for producing the message content.`
		}
	default:
		messageText, truncated = notify.TruncateInRunes(tmpl(n.conf.Message), maxMessageLenRunes)
		if err != nil {
			return false, err
		}
		if truncated {
			logger.Warn("Truncated message", "max_runes", maxMessageLenRunes)
		}
	}

	n.client.Token, err = n.getBotToken()
	if err != nil {
		return true, err
	}

	chatID, err := n.getChatID()
	if err != nil {
		return true, err
	}

	message, err := n.client.Send(telebot.ChatID(chatID), messageText, &telebot.SendOptions{
		DisableNotification:   n.conf.DisableNotifications,
		DisableWebPagePreview: true,
		ThreadID:              n.conf.MessageThreadID,
		ParseMode:             n.conf.ParseMode,
	})
	if err != nil {
		return true, err
	}
	logger.Debug("Telegram message successfully published", "message_id", message.ID, "chat_id", message.Chat.ID)

	return false, nil
}

func createTelegramClient(apiURL, parseMode string, httpClient *http.Client) (*telebot.Bot, error) {
	bot, err := telebot.NewBot(telebot.Settings{
		URL:       apiURL,
		ParseMode: parseMode,
		Client:    httpClient,
		Offline:   true,
	})
	if err != nil {
		return nil, err
	}

	return bot, nil
}

func (n *Notifier) getBotToken() (string, error) {
	if len(n.conf.BotTokenFile) > 0 {
		content, err := os.ReadFile(n.conf.BotTokenFile)
		if err != nil {
			return "", fmt.Errorf("could not read %s: %w", n.conf.BotTokenFile, err)
		}
		return strings.TrimSpace(string(content)), nil
	}
	return string(n.conf.BotToken), nil
}

func (n *Notifier) getChatID() (int64, error) {
	if len(n.conf.ChatIDFile) > 0 {
		content, err := os.ReadFile(n.conf.ChatIDFile)
		if err != nil {
			return 0, fmt.Errorf("could not read %s: %w", n.conf.ChatIDFile, err)
		}
		chatID, err := strconv.ParseInt(strings.TrimSpace(string(content)), 10, 64)
		if err != nil {
			return 0, fmt.Errorf("could not parse chat_id from %s: %w", n.conf.ChatIDFile, err)
		}
		return chatID, nil
	}
	return n.conf.ChatID, nil
}
