package telegram

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	notconfig "github.com/tech/sendico/notification/interface/services/notification/config"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"github.com/tech/sendico/pkg/model"
	"go.uber.org/zap"
)

const defaultAPIURL = "https://api.telegram.org"

type Client interface {
	SendDemoRequest(ctx context.Context, request *model.DemoRequest) error
	SendContactRequest(ctx context.Context, request *model.ContactRequest) error
	SendCallRequest(ctx context.Context, request *model.CallRequest) error
	SendText(ctx context.Context, chatID string, text string, replyToMessageID string) (*model.TelegramMessage, error)
	SetMessageReaction(ctx context.Context, chatID string, messageID string, emoji string) error
}

type client struct {
	logger     mlogger.Logger
	httpClient *http.Client
	apiURL     string
	botToken   string
	chatID     string
	threadID   *int64
	parseMode  parseMode
}

type sendMessagePayload struct {
	ChatID           string `json:"chat_id"`
	Text             string `json:"text"`
	ParseMode        string `json:"parse_mode,omitempty"`
	ThreadID         *int64 `json:"message_thread_id,omitempty"`
	ReplyToMessageID *int64 `json:"reply_to_message_id,omitempty"`
	DisablePreview   bool   `json:"disable_web_page_preview,omitempty"`
	DisableNotify    bool   `json:"disable_notification,omitempty"`
	ProtectContent   bool   `json:"protect_content,omitempty"`
}

func NewClient(logger mlogger.Logger, cfg *notconfig.TelegramConfig) (Client, error) {
	if cfg == nil {
		return nil, merrors.InvalidArgument("telegram configuration is not provided", "config.notification.telegram")
	}
	token := strings.TrimSpace(os.Getenv(cfg.BotTokenEnv))
	if token == "" {
		return nil, merrors.InvalidArgument(fmt.Sprintf("telegram bot token env %s is empty", cfg.BotTokenEnv), cfg.BotTokenEnv)
	}
	chatID := strings.TrimSpace(os.Getenv(cfg.ChatIDEnv))
	if chatID == "" {
		return nil, merrors.InvalidArgument(fmt.Sprintf("telegram chat id env %s is empty", cfg.ChatIDEnv), cfg.ChatIDEnv)
	}

	var threadID *int64
	if env := strings.TrimSpace(cfg.ThreadIDEnv); env != "" {
		raw := strings.TrimSpace(os.Getenv(env))
		if raw != "" {
			val, err := strconv.ParseInt(raw, 10, 64)
			if err != nil {
				return nil, merrors.InvalidArgumentWrap(err, fmt.Sprintf("telegram thread id env %s is invalid", env), env)
			}
			threadID = &val
		}
	}

	timeout := time.Duration(cfg.TimeoutSeconds) * time.Second
	if timeout <= 0 {
		timeout = 10 * time.Second
	}

	apiURL := strings.TrimSpace(cfg.APIURL)
	if apiURL == "" {
		apiURL = defaultAPIURL
	}
	mode := normalizeParseMode(cfg.ParseMode)
	if mode == parseModeUnset {
		mode = parseModeMarkdown
	}

	return &client{
		logger: logger.Named("telegram"),
		httpClient: &http.Client{
			Timeout: timeout,
		},
		apiURL:    strings.TrimRight(apiURL, "/"),
		botToken:  token,
		chatID:    chatID,
		threadID:  threadID,
		parseMode: mode,
	}, nil
}

func (c *client) SendDemoRequest(ctx context.Context, request *model.DemoRequest) error {
	if request == nil {
		return merrors.InvalidArgument("demo request payload is nil", "request")
	}
	return c.sendForm(ctx, newDemoRequestTemplate(request))
}

type sendMessageResponse struct {
	OK          bool             `json:"ok"`
	Result      *messageResponse `json:"result,omitempty"`
	Description string           `json:"description,omitempty"`
}

type messageResponse struct {
	MessageID      int64            `json:"message_id"`
	Date           int64            `json:"date"`
	Chat           messageChat      `json:"chat"`
	From           *messageUser     `json:"from,omitempty"`
	Text           string           `json:"text"`
	ReplyToMessage *messageResponse `json:"reply_to_message,omitempty"`
}

type messageChat struct {
	ID int64 `json:"id"`
}

type messageUser struct {
	ID       int64  `json:"id"`
	Username string `json:"username,omitempty"`
}

type setMessageReactionPayload struct {
	ChatID    string         `json:"chat_id"`
	MessageID int64          `json:"message_id"`
	Reaction  []reactionType `json:"reaction,omitempty"`
	IsBig     bool           `json:"is_big,omitempty"`
}

type reactionType struct {
	Type          string `json:"type"`
	Emoji         string `json:"emoji,omitempty"`
	CustomEmojiID string `json:"custom_emoji_id,omitempty"`
}

type setMessageReactionResponse struct {
	OK          bool   `json:"ok"`
	Result      bool   `json:"result,omitempty"`
	Description string `json:"description,omitempty"`
}

func (c *client) sendMessage(ctx context.Context, payload sendMessagePayload) (*model.TelegramMessage, error) {
	body, err := json.Marshal(&payload)
	if err != nil {
		c.logger.Warn("Failed to marshal telegram payload", zap.Error(err))
		return nil, err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpoint(), bytes.NewReader(body))
	if err != nil {
		c.logger.Warn("Failed to create telegram request", zap.Error(err))
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")

	fields := []zap.Field{
		zap.String("chat_id", payload.ChatID),
		zap.Int("payload_size_bytes", len(body)),
		zap.Bool("disable_preview", payload.DisablePreview),
		zap.Bool("disable_notification", payload.DisableNotify),
		zap.Bool("protect_content", payload.ProtectContent),
	}
	if payload.ThreadID != nil {
		fields = append(fields, zap.Int64("thread_id", *payload.ThreadID))
	}
	if payload.ReplyToMessageID != nil {
		fields = append(fields, zap.Int64("reply_to_message_id", *payload.ReplyToMessageID))
	}
	c.logger.Debug("Sending Telegram message", fields...)
	start := time.Now()

	resp, err := c.httpClient.Do(req)
	if err != nil {
		c.logger.Warn("Telegram request failed", zap.Error(err))
		return nil, err
	}
	defer func() {
		if closeErr := resp.Body.Close(); closeErr != nil {
			c.logger.Warn("Failed to close telegram response body", zap.Error(closeErr))
		}
	}()

	respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 16<<10))
	if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
		var parsed sendMessageResponse
		if err := json.Unmarshal(respBody, &parsed); err != nil {
			c.logger.Warn("Failed to decode telegram response", zap.Error(err))
			return nil, err
		}
		if !parsed.OK || parsed.Result == nil {
			msg := "telegram sendMessage response missing result"
			if parsed.Description != "" {
				msg = parsed.Description
			}
			return nil, merrors.Internal(msg)
		}
		c.logger.Debug("Telegram message sent", zap.Int("status_code", resp.StatusCode), zap.Duration("latency", time.Since(start)))
		return toTelegramMessage(parsed.Result), nil
	}
	c.logger.Warn("Telegram API returned non-success status",
		zap.Int("status_code", resp.StatusCode),
		zap.ByteString("response_body", respBody),
		zap.String("chat_id", c.chatID))
	return nil, merrors.Internal(fmt.Sprintf("telegram sendMessage failed with status %d: %s", resp.StatusCode, string(respBody)))
}

func (c *client) endpoint() string {
	return c.endpointFor("sendMessage")
}

func (c *client) endpointFor(method string) string {
	method = strings.TrimPrefix(strings.TrimSpace(method), "/")
	return fmt.Sprintf("%s/bot%s/%s", c.apiURL, c.botToken, method)
}

func (c *client) SendContactRequest(ctx context.Context, request *model.ContactRequest) error {
	if request == nil {
		return merrors.InvalidArgument("contact request payload is nil", "request")
	}
	return c.sendForm(ctx, newContactRequestTemplate(request))
}

func (c *client) SendCallRequest(ctx context.Context, request *model.CallRequest) error {
	if request == nil {
		return merrors.InvalidArgument("call request payload is nil", "request")
	}
	return c.sendForm(ctx, newCallRequestTemplate(request))
}

func (c *client) sendForm(ctx context.Context, template messageTemplate) error {
	message := template.Format(c.parseMode)
	payload := sendMessagePayload{
		ChatID:         c.chatID,
		Text:           message,
		ParseMode:      c.parseMode.String(),
		ThreadID:       c.threadID,
		DisablePreview: true,
	}
	_, err := c.sendMessage(ctx, payload)
	return err
}

func (c *client) SendText(ctx context.Context, chatID string, text string, replyToMessageID string) (*model.TelegramMessage, error) {
	chatID = strings.TrimSpace(chatID)
	if chatID == "" {
		chatID = c.chatID
	}
	if chatID == "" {
		return nil, merrors.InvalidArgument("telegram chat id is empty", "chat_id")
	}
	payload := sendMessagePayload{
		ChatID:         chatID,
		Text:           text,
		ParseMode:      c.parseMode.String(),
		ThreadID:       c.threadID,
		DisablePreview: true,
	}
	if replyToMessageID != "" {
		val, err := strconv.ParseInt(replyToMessageID, 10, 64)
		if err != nil {
			return nil, merrors.InvalidArgumentWrap(err, "invalid reply_to_message_id", "reply_to_message_id")
		}
		payload.ReplyToMessageID = &val
	}
	return c.sendMessage(ctx, payload)
}

func (c *client) SetMessageReaction(ctx context.Context, chatID string, messageID string, emoji string) error {
	chatID = strings.TrimSpace(chatID)
	if chatID == "" {
		return merrors.InvalidArgument("telegram chat id is empty", "chat_id")
	}
	msgID, err := strconv.ParseInt(strings.TrimSpace(messageID), 10, 64)
	if err != nil {
		return merrors.InvalidArgumentWrap(err, "invalid message_id", "message_id")
	}
	emoji = strings.TrimSpace(emoji)
	reaction := []reactionType{}
	if emoji != "" {
		reaction = append(reaction, reactionType{Type: "emoji", Emoji: emoji})
	}
	payload := setMessageReactionPayload{
		ChatID:    chatID,
		MessageID: msgID,
		Reaction:  reaction,
	}
	return c.sendReaction(ctx, payload)
}

func (c *client) sendReaction(ctx context.Context, payload setMessageReactionPayload) error {
	body, err := json.Marshal(&payload)
	if err != nil {
		c.logger.Warn("Failed to marshal telegram reaction payload", zap.Error(err))
		return err
	}
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpointFor("setMessageReaction"), bytes.NewReader(body))
	if err != nil {
		c.logger.Warn("Failed to create telegram reaction request", zap.Error(err))
		return err
	}
	req.Header.Set("Content-Type", "application/json")

	fields := []zap.Field{
		zap.String("chat_id", payload.ChatID),
		zap.Int64("message_id", payload.MessageID),
		zap.Int("payload_size_bytes", len(body)),
	}
	c.logger.Debug("Sending Telegram reaction", fields...)
	start := time.Now()

	resp, err := c.httpClient.Do(req)
	if err != nil {
		c.logger.Warn("Telegram reaction request failed", zap.Error(err))
		return err
	}
	defer func() {
		if closeErr := resp.Body.Close(); closeErr != nil {
			c.logger.Warn("Failed to close telegram reaction response body", zap.Error(closeErr))
		}
	}()

	respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 16<<10))
	if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
		var parsed setMessageReactionResponse
		if err := json.Unmarshal(respBody, &parsed); err != nil {
			c.logger.Warn("Failed to decode telegram reaction response", zap.Error(err))
			return err
		}
		if !parsed.OK || !parsed.Result {
			msg := "telegram setMessageReaction response missing result"
			if parsed.Description != "" {
				msg = parsed.Description
			}
			return merrors.Internal(msg)
		}
		c.logger.Debug("Telegram reaction sent", zap.Int("status_code", resp.StatusCode), zap.Duration("latency", time.Since(start)))
		return nil
	}
	c.logger.Warn("Telegram API returned non-success status for reaction",
		zap.Int("status_code", resp.StatusCode),
		zap.ByteString("response_body", respBody),
		zap.String("chat_id", payload.ChatID))
	return merrors.Internal(fmt.Sprintf("telegram setMessageReaction failed with status %d: %s", resp.StatusCode, string(respBody)))
}

func toTelegramMessage(msg *messageResponse) *model.TelegramMessage {
	if msg == nil {
		return nil
	}
	result := &model.TelegramMessage{
		ChatID:    strconv.FormatInt(msg.Chat.ID, 10),
		MessageID: strconv.FormatInt(msg.MessageID, 10),
		Text:      msg.Text,
		SentAt:    msg.Date,
	}
	if msg.From != nil {
		result.FromUserID = strconv.FormatInt(msg.From.ID, 10)
		result.FromUsername = msg.From.Username
	}
	if msg.ReplyToMessage != nil {
		result.ReplyToMessageID = strconv.FormatInt(msg.ReplyToMessage.MessageID, 10)
	}
	return result
}
