// Package telegram is the Bot API client and command handler
// for the broad-announce M3 milestone (SPEC §8).
//
// The package is split into three files:
//
//   - client.go   — BotClient interface + HTTP impl + fakes
//   - commands.go — text → Command parser
//   - handler.go  — Command → DB updates
//
// M3 uses long-polling (cmd/telegramd). Webhook mode is M5/M9.
//
// All bot tokens, chat IDs, and user IDs are in the bot's
// per-company namespace; the client takes a bot token as
// input on every call so the same client can be reused
// across bots in a multi-bot future.
package telegram

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"
)

// Update is the subset of the Telegram Update object we care
// about. We only need the message (incoming text) and the
// update_id (offset for long-polling).
type Update struct {
	UpdateID int64   `json:"update_id"`
	Message  *Message `json:"message,omitempty"`
}

// Message is the subset of Message we care about.
type Message struct {
	MessageID int64  `json:"message_id"`
	From      *User  `json:"from,omitempty"`
	Chat      Chat   `json:"chat"`
	Text      string `json:"text,omitempty"`
	Date      int64  `json:"date,omitempty"`
}

// User is the subset of User we care about.
type User struct {
	ID        int64  `json:"id"`
	IsBot     bool   `json:"is_bot"`
	FirstName string `json:"first_name"`
	Username  string `json:"username,omitempty"`
}

// Chat is the subset of Chat we care about.
type Chat struct {
	ID   int64  `json:"id"`
	Type string `json:"type"` // private, group, supergroup, channel
}

// SentMessage is the API response from sendMessage.
type SentMessage struct {
	MessageID int64 `json:"message_id"`
	Chat      Chat  `json:"chat"`
	Date      int64 `json:"date"`
	Text      string `json:"text"`
}

// BotClient is the minimal interface deliverd-telegram and
// telegramd need. It can be swapped for a fake in tests
// (faketgmd is a fake SERVER; this is the client-side
// interface for swapping in process-local fakes).
type BotClient interface {
	SendMessage(ctx context.Context, token string, chatID int64, text string) (*SentMessage, error)
	GetUpdates(ctx context.Context, token string, offset int64, timeoutSec int) ([]Update, error)
}

// HTTPBotClient is the real-HTTP implementation, hitting
// https://api.telegram.org/bot<token>/...
type HTTPBotClient struct {
	BaseURL string       // override for tests; default https://api.telegram.org
	HTTP    *http.Client // override for tests
}

// NewHTTPBotClient returns a client with sensible defaults.
func NewHTTPBotClient() *HTTPBotClient {
	return &HTTPBotClient{
		BaseURL: "https://api.telegram.org",
		HTTP:    &http.Client{Timeout: 60 * time.Second},
	}
}

// SendMessage posts a text message to a chat.
func (c *HTTPBotClient) SendMessage(ctx context.Context, token string, chatID int64, text string) (*SentMessage, error) {
	url := fmt.Sprintf("%s/bot%s/sendMessage", c.BaseURL, token)
	body, _ := json.Marshal(map[string]any{
		"chat_id": chatID,
		"text":    text,
	})
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := c.HTTP.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode/100 != 2 {
		respBody, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("sendMessage status %d: %s", resp.StatusCode, string(respBody))
	}
	var out struct {
		OK     bool         `json:"ok"`
		Result *SentMessage `json:"result"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		return nil, err
	}
	if !out.OK || out.Result == nil {
		return nil, fmt.Errorf("sendMessage not ok: %+v", out)
	}
	return out.Result, nil
}

// GetUpdates long-polls for new updates. Telegram holds the
// connection for up to `timeoutSec` seconds. The returned slice
// can be empty; the caller should advance offset to the last
// update_id+1 and call again.
func (c *HTTPBotClient) GetUpdates(ctx context.Context, token string, offset int64, timeoutSec int) ([]Update, error) {
	url := fmt.Sprintf("%s/bot%s/getUpdates", c.BaseURL, token)
	body, _ := json.Marshal(map[string]any{
		"offset":  offset,
		"timeout": timeoutSec,
		"allowed_updates": []string{"message"},
	})
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	resp, err := c.HTTP.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode/100 != 2 {
		respBody, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("getUpdates status %d: %s", resp.StatusCode, string(respBody))
	}
	var out struct {
		OK     bool     `json:"ok"`
		Result []Update `json:"result"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		return nil, err
	}
	if !out.OK {
		return nil, fmt.Errorf("getUpdates not ok: %+v", out)
	}
	return out.Result, nil
}
