package message

import (
	"context"
	"strconv"
	"time"

	"github.com/go-faster/errors"

	"go.mau.fi/mautrix-telegram/pkg/gotd/crypto"
	"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/message/entity"
	"go.mau.fi/mautrix-telegram/pkg/gotd/telegram/message/styling"
	"go.mau.fi/mautrix-telegram/pkg/gotd/tg"
)

type pollAnswerBuilder struct {
	input *tg.InputMediaPoll
}

// PollAnswerOption is an option to create poll answer.
type PollAnswerOption func(p *pollAnswerBuilder)

// RawPollAnswer creates new raw poll answer option.
func RawPollAnswer(poll *tg.PollAnswer) PollAnswerOption {
	return func(p *pollAnswerBuilder) {
		p.input.Poll.Answers = append(p.input.Poll.Answers, poll)
	}
}

// PollAnswer creates new plain poll answer option.
func PollAnswer(text string, entities ...tg.MessageEntityClass) PollAnswerOption {
	return func(p *pollAnswerBuilder) {
		i := len(p.input.Poll.Answers)
		p.input.Poll.Answers = append(p.input.Poll.Answers, &tg.PollAnswer{
			Text:   tg.TextWithEntities{Text: text, Entities: entities},
			Option: []byte(strconv.Itoa(i)),
		})
	}
}

// CorrectPollAnswer creates new correct poll answer option.
func CorrectPollAnswer(text string, entities ...tg.MessageEntityClass) PollAnswerOption {
	return func(p *pollAnswerBuilder) {
		p.input.Poll.Quiz = true
		i := len(p.input.Poll.Answers)
		option := []byte(strconv.Itoa(i))
		p.input.Poll.Answers = append(p.input.Poll.Answers, &tg.PollAnswer{
			Text:   tg.TextWithEntities{Text: text, Entities: entities},
			Option: option,
		})
		p.input.CorrectAnswers = append(p.input.CorrectAnswers, i)
	}
}

// PollBuilder is a Poll media option.
type PollBuilder struct {
	input   tg.InputMediaPoll
	answers []PollAnswerOption
	opts    []StyledTextOption
}

// PollID return poll ID. If poll was not sent, will be zero.
// It useful to close polls.
func (p *PollBuilder) PollID() int64 {
	return p.input.Poll.ID
}

// Close sets flag that the poll is closed and doesn't accept any more answers.
func (p *PollBuilder) Close() *PollBuilder {
	p.input.Poll.Closed = true
	return p
}

// PublicVoters sets flag that votes are publicly visible to all users (non-anonymous poll).
func (p *PollBuilder) PublicVoters(publicVoters bool) *PollBuilder {
	p.input.Poll.PublicVoters = publicVoters
	return p
}

// MultipleChoice sets flag that multiple options can be chosen as answer.
func (p *PollBuilder) MultipleChoice(multipleChoice bool) *PollBuilder {
	p.input.Poll.MultipleChoice = multipleChoice
	return p
}

// CloseDate sets point in time (Unix timestamp) when the poll will be automatically closed.
// Must be at least 5 and no more than 600 seconds in the future.
func (p *PollBuilder) CloseDate(d time.Time) *PollBuilder {
	return p.CloseDateTS(int(d.Unix()))
}

// CloseDateTS sets point in time (Unix timestamp) when the poll will be automatically closed.
// Must be at least 5 and no more than 600 seconds in the future.
func (p *PollBuilder) CloseDateTS(ts int) *PollBuilder {
	p.input.Poll.ClosePeriod = 0
	p.input.Poll.CloseDate = ts
	return p
}

// ClosePeriod sets amount of time in seconds the poll will be active after creation, 5-600 seconds.
func (p *PollBuilder) ClosePeriod(d time.Duration) *PollBuilder {
	return p.ClosePeriodSeconds(int(d.Seconds()))
}

// ClosePeriodSeconds sets amount of time in seconds the poll will be active after creation, 5-600.
func (p *PollBuilder) ClosePeriodSeconds(s int) *PollBuilder {
	p.input.Poll.ClosePeriod = s
	p.input.Poll.CloseDate = 0
	return p
}

// Explanation sets explanation message.
func (p *PollBuilder) Explanation(msg string) *PollBuilder {
	p.input.Solution = msg
	p.input.SolutionEntities = nil
	return p
}

// StyledExplanation sets styled explanation message.
func (p *PollBuilder) StyledExplanation(texts ...StyledTextOption) *PollBuilder {
	p.opts = texts
	return p
}

func (p *PollBuilder) apply(ctx context.Context, b *multiMediaBuilder) error {
	if p.input.Poll.ID == 0 {
		id, err := crypto.RandInt64(b.sender.rand)
		if err != nil {
			return errors.Wrap(err, "generate id")
		}

		p.input.Poll.ID = id
	}

	if len(p.opts) > 0 {
		tb := entity.Builder{}
		if err := styling.Perform(&tb, p.opts...); err != nil {
			return err
		}
		p.input.Solution, p.input.SolutionEntities = tb.Complete()
	}

	pb := pollAnswerBuilder{input: &p.input}
	for _, opt := range p.answers {
		opt(&pb)
	}

	return Media(&p.input).apply(ctx, b)
}

func (p *PollBuilder) QuestionEntities(entities []tg.MessageEntityClass) *PollBuilder {
	p.input.Poll.Question.Entities = entities
	return p
}

// Poll adds poll attachment.
//
// To set poll question entities, use [QuestionEntities](#PollBuilder.QuestionEntities).
func Poll(question string, a, b PollAnswerOption, answers ...PollAnswerOption) *PollBuilder {
	return &PollBuilder{
		input: tg.InputMediaPoll{
			Poll: tg.Poll{
				Question: tg.TextWithEntities{Text: question},
			},
		},
		answers: append([]PollAnswerOption{a, b}, answers...),
	}
}

// PollVote votes in a poll.
func (b *RequestBuilder) PollVote(
	ctx context.Context, msgID int,
	answer []byte, answers ...[]byte,
) (tg.UpdatesClass, error) {
	p, err := b.peer(ctx)
	if err != nil {
		return nil, errors.Wrap(err, "peer")
	}

	upd, err := b.sender.sendVote(ctx, &tg.MessagesSendVoteRequest{
		Peer:    p,
		MsgID:   msgID,
		Options: append([][]byte{answer}, answers...),
	})
	if err != nil {
		return nil, errors.Wrap(err, "start bot")
	}

	return upd, nil
}
