package tgbotapi

import (
	"encoding/json"
	"io"
	"net/url"
	"strconv"
)

// Telegram constants
const (
	// APIEndpoint is the endpoint for all API methods, with formatting for Sprintf
	APIEndpoint = "https://api.telegram.org/bot%s/%s"
	// FileEndpoint is the endpoint for downloading a file from Telegram
	FileEndpoint = "https://api.telegram.org/file/bot%s/%s"
)

// Constant values for ChatActions
const (
	ChatTyping         = "typing"
	ChatUploadPhoto    = "upload_photo"
	ChatRecordVideo    = "record_video"
	ChatUploadVideo    = "upload_video"
	ChatRecordAudio    = "record_audio"
	ChatUploadAudio    = "upload_audio"
	ChatUploadDocument = "upload_document"
	ChatFindLocation   = "find_location"
)

// API errors
const (
	// APIForbidden happens when a token is bad
	APIForbidden = "forbidden"
)

// Constant values for ParseMode in MessageConfig
const (
	ModeMarkdown = "Markdown"
)

type Chattable interface {
	Values() (url.Values, error)
	Method() string
}

type Fileable interface {
	Chattable
	Params() (map[string]string, error)
	Name() string
	GetFile() interface{}
	UseExistingFile() bool
}

// Base struct for all chat event(Message, Photo and so on)
type BaseChat struct {
	ChatID           int
	ChannelUsername  string
	ReplyToMessageID int
	ReplyMarkup      interface{}
}

func (chat *BaseChat) Values() (url.Values, error) {
	v := url.Values{}
	if chat.ChannelUsername != "" {
		v.Add("chat_id", chat.ChannelUsername)
	} else {
		v.Add("chat_id", strconv.Itoa(chat.ChatID))
	}

	if chat.ReplyToMessageID != 0 {
		v.Add("reply_to_message_id", strconv.Itoa(chat.ReplyToMessageID))
	}

	if chat.ReplyMarkup != nil {
		data, err := json.Marshal(chat.ReplyMarkup)
		if err != nil {
			return v, err
		}

		v.Add("reply_markup", string(data))
	}

	return v, nil
}

type BaseFile struct {
	BaseChat
	FilePath    string
	File        interface{}
	FileID      string
	UseExisting bool
}

func (file BaseFile) Params() (map[string]string, error) {
	params := make(map[string]string)

	if file.ChannelUsername != "" {
		params["chat_id"] = file.ChannelUsername
	} else {
		params["chat_id"] = strconv.Itoa(file.ChatID)
	}

	if file.ReplyToMessageID != 0 {
		params["reply_to_message_id"] = strconv.Itoa(file.ReplyToMessageID)
	}

	if file.ReplyMarkup != nil {
		data, err := json.Marshal(file.ReplyMarkup)
		if err != nil {
			return params, err
		}

		params["reply_markup"] = string(data)
	}

	return params, nil
}

func (file BaseFile) GetFile() interface{} {
	var result interface{}
	if file.FilePath == "" {
		result = file.File
	} else {
		result = file.FilePath
	}

	return result
}

func (file BaseFile) UseExistingFile() bool {
	return file.UseExisting
}

// MessageConfig contains information about a SendMessage request.
type MessageConfig struct {
	BaseChat
	Text                  string
	ParseMode             string
	DisableWebPagePreview bool
	ReplyMarkup           interface{}
}

func (config MessageConfig) Values() (url.Values, error) {
	v, _ := config.BaseChat.Values()
	v.Add("text", config.Text)
	v.Add("disable_web_page_preview", strconv.FormatBool(config.DisableWebPagePreview))
	if config.ParseMode != "" {
		v.Add("parse_mode", config.ParseMode)
	}

	return v, nil
}

func (config MessageConfig) Method() string {
	return "SendMessage"
}

// ForwardConfig contains information about a ForwardMessage request.
type ForwardConfig struct {
	BaseChat
	FromChatID          int
	FromChannelUsername string
	MessageID           int
}

func (config ForwardConfig) Values() (url.Values, error) {
	v, _ := config.BaseChat.Values()
	v.Add("from_chat_id", strconv.Itoa(config.FromChatID))
	v.Add("message_id", strconv.Itoa(config.MessageID))
	return v, nil
}

func (config ForwardConfig) Method() string {
	return "forwardMessage"
}

// PhotoConfig contains information about a SendPhoto request.
type PhotoConfig struct {
	BaseFile
	Caption string
}

func (config PhotoConfig) Params() (map[string]string, error) {
	params, _ := config.BaseFile.Params()

	if config.Caption != "" {
		params["caption"] = config.Caption
	}

	return params, nil
}

func (config PhotoConfig) Values() (url.Values, error) {
	v, _ := config.BaseChat.Values()

	v.Add(config.Name(), config.FileID)
	if config.Caption != "" {
		v.Add("caption", config.Caption)
	}
	return v, nil
}

func (config PhotoConfig) Name() string {
	return "photo"
}

func (config PhotoConfig) Method() string {
	return "SendPhoto"
}

// AudioConfig contains information about a SendAudio request.
type AudioConfig struct {
	BaseFile
	Duration  int
	Performer string
	Title     string
}

func (config AudioConfig) Values() (url.Values, error) {
	v, _ := config.BaseChat.Values()

	v.Add(config.Name(), config.FileID)
	if config.Duration != 0 {
		v.Add("duration", strconv.Itoa(config.Duration))
	}

	if config.Performer != "" {
		v.Add("performer", config.Performer)
	}
	if config.Title != "" {
		v.Add("title", config.Title)
	}

	return v, nil
}

func (config AudioConfig) Params() (map[string]string, error) {
	params, _ := config.BaseFile.Params()

	if config.Duration != 0 {
		params["duration"] = strconv.Itoa(config.Duration)
	}

	if config.Performer != "" {
		params["performer"] = config.Performer
	}
	if config.Title != "" {
		params["title"] = config.Title
	}

	return params, nil
}

func (config AudioConfig) Name() string {
	return "audio"
}

func (config AudioConfig) Method() string {
	return "SendAudio"
}

// DocumentConfig contains information about a SendDocument request.
type DocumentConfig struct {
	BaseFile
}

func (config DocumentConfig) Values() (url.Values, error) {
	v, _ := config.BaseChat.Values()

	v.Add(config.Name(), config.FileID)

	return v, nil
}

func (config DocumentConfig) Params() (map[string]string, error) {
	params, _ := config.BaseFile.Params()

	return params, nil
}

func (config DocumentConfig) Name() string {
	return "document"
}

func (config DocumentConfig) Method() string {
	return "sendDocument"
}

// StickerConfig contains information about a SendSticker request.
type StickerConfig struct {
	BaseFile
}

func (config StickerConfig) Values() (url.Values, error) {
	v, _ := config.BaseChat.Values()

	v.Add(config.Name(), config.FileID)

	return v, nil
}

func (config StickerConfig) Params() (map[string]string, error) {
	params, _ := config.BaseFile.Params()

	return params, nil
}

func (config StickerConfig) Name() string {
	return "sticker"
}

func (config StickerConfig) Method() string {
	return "sendSticker"
}

// VideoConfig contains information about a SendVideo request.
type VideoConfig struct {
	BaseFile
	Duration int
	Caption  string
}

func (config VideoConfig) Values() (url.Values, error) {
	v, _ := config.BaseChat.Values()

	v.Add(config.Name(), config.FileID)
	if config.Duration != 0 {
		v.Add("duration", strconv.Itoa(config.Duration))
	}
	if config.Caption != "" {
		v.Add("caption", config.Caption)
	}

	return v, nil
}

func (config VideoConfig) Params() (map[string]string, error) {
	params, _ := config.BaseFile.Params()

	return params, nil
}

func (config VideoConfig) Name() string {
	return "video"
}

func (config VideoConfig) Method() string {
	return "sendVideo"
}

// VoiceConfig contains information about a SendVoice request.
type VoiceConfig struct {
	BaseFile
	Duration int
}

func (config VoiceConfig) Values() (url.Values, error) {
	v, _ := config.BaseChat.Values()

	v.Add(config.Name(), config.FileID)
	if config.Duration != 0 {
		v.Add("duration", strconv.Itoa(config.Duration))
	}

	return v, nil
}

func (config VoiceConfig) Params() (map[string]string, error) {
	params, _ := config.BaseFile.Params()

	if config.Duration != 0 {
		params["duration"] = strconv.Itoa(config.Duration)
	}

	return params, nil
}

func (config VoiceConfig) Name() string {
	return "voice"
}

func (config VoiceConfig) Method() string {
	return "sendVoice"
}

// LocationConfig contains information about a SendLocation request.
type LocationConfig struct {
	BaseChat
	Latitude  float64
	Longitude float64
}

func (config LocationConfig) Values() (url.Values, error) {
	v, _ := config.BaseChat.Values()

	v.Add("latitude", strconv.FormatFloat(config.Latitude, 'f', 6, 64))
	v.Add("longitude", strconv.FormatFloat(config.Longitude, 'f', 6, 64))

	return v, nil
}

func (config LocationConfig) Method() string {
	return "sendLocation"
}

// ChatActionConfig contains information about a SendChatAction request.
type ChatActionConfig struct {
	BaseChat
	Action string
}

func (config ChatActionConfig) Values() (url.Values, error) {
	v, _ := config.BaseChat.Values()
	v.Add("action", config.Action)
	return v, nil
}

func (config ChatActionConfig) Method() string {
	return "sendChatAction"
}

// UserProfilePhotosConfig contains information about a GetUserProfilePhotos request.
type UserProfilePhotosConfig struct {
	UserID int
	Offset int
	Limit  int
}

// FileConfig has information about a file hosted on Telegram
type FileConfig struct {
	FileID string
}

// UpdateConfig contains information about a GetUpdates request.
type UpdateConfig struct {
	Offset  int
	Limit   int
	Timeout int
}

// WebhookConfig contains information about a SetWebhook request.
type WebhookConfig struct {
	URL         *url.URL
	Certificate interface{}
}

// FileBytes contains information about a set of bytes to upload as a File.
type FileBytes struct {
	Name  string
	Bytes []byte
}

// FileReader contains information about a reader to upload as a File.
// If Size is -1, it will read the entire Reader into memory to calculate a Size.
type FileReader struct {
	Name   string
	Reader io.Reader
	Size   int64
}
