package tmi

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"time"

	"code.justin.tv/foundation/twitchclient"

	"context"
)

type GlobalBannedWordsMessage struct {
	GlobalBannedWords []GlobalBannedWord `json:"global_banned_words"`
}

type ChannelBannedWordsMessage struct {
	ChannelBannedWords []ChannelBannedWord `json:"channel_banned_words"`
}

type ChannelPermittedWordsMessage struct {
	ChannelPermittedWords []ChannelPermittedWord `json:"channel_permitted_words"`
}

type Phrases []string

type GlobalBannedWord struct {
	Word      string `json:"word"`
	CanOptOut bool   `json:"can_optout"`
}

type ChannelModeratedWord struct {
	Category      string     `json:"-"`
	Phrases       []string   `json:"phrases"`
	CreatedAt     *time.Time `json:"created_at"`
	UpdatedAt     *time.Time `json:"updated_at"`
	ExpiresAt     *time.Time `json:"expires_at"`
	ExpiresIn     *string    `json:"expires_in"`
	Source        *string    `json:"-"`
	IsModEditable bool       `json:"is_mod_editable"`
}

type ChannelBannedWord ChannelModeratedWord

type ChannelPermittedWord ChannelModeratedWord

type AddChannelBannedWordParams struct {
	ChannelID string            `json:"channel_id"`
	UserID    string            `json:"user_id"`
	Word      ChannelBannedWord `json:"word"`
}

type AddChannelBlockedTermResponse struct {
	ResponseCode string    `json:"response_code"`
	Phrases      Phrases   `json:"phrases"`
	AddedAt      time.Time `json:"added_at"`
}

type DeleteChannelBannedWordParams struct {
	ChannelID string  `json:"channel_id"`
	UserID    string  `json:"user_id"`
	Phrases   Phrases `json:"phrases"`
}

type DeleteChannelBlockedTermResponse struct {
	ResponseCode string    `json:"response_code"`
	Phrases      Phrases   `json:"phrases"`
	DeletedAt    time.Time `json:"deleted_at"`
}

type AddChannelPermittedWordParams struct {
	ChannelID string               `json:"channel_id"`
	UserID    string               `json:"user_id"`
	Word      ChannelPermittedWord `json:"word"`
}

type AddChannelPermittedTermResponse struct {
	ResponseCode string    `json:"response_code"`
	Phrases      Phrases   `json:"phrases"`
	AddedAt      time.Time `json:"added_at"`
}

type DeleteChannelPermittedWordParams struct {
	ChannelID string  `json:"channel_id"`
	UserID    string  `json:"user_id"`
	Phrases   Phrases `json:"phrases"`
}

type DeleteChannelPermittedTermResponse struct {
	ResponseCode string    `json:"response_code"`
	Phrases      Phrases   `json:"phrases"`
	DeletedAt    time.Time `json:"deleted_at"`
}

func (c *clientImpl) GlobalBannedWords(ctx context.Context, reqOpts *twitchclient.ReqOpts) (*GlobalBannedWordsMessage, error) {
	path := "/global/banned_words"
	req, err := c.NewRequest("GET", path, nil)
	if err != nil {
		return nil, err
	}

	combinedReqOpts := twitchclient.MergeReqOpts(reqOpts, twitchclient.ReqOpts{
		StatName:       "service.clue.global_banned_words",
		StatSampleRate: defaultStatSampleRate,
	})

	var decoded GlobalBannedWordsMessage
	if _, err := c.DoJSON(ctx, &decoded, req, combinedReqOpts); err != nil {
		return nil, err
	}
	return &decoded, nil
}

func (c *clientImpl) ChannelBannedWords(ctx context.Context, channelID, requesterUserID string, reqOpts *twitchclient.ReqOpts) (*ChannelBannedWordsMessage, error) {
	path := fmt.Sprintf("/rooms/%s/banned_words/%s", url.QueryEscape(channelID), url.QueryEscape(requesterUserID))
	req, err := c.NewRequest("GET", path, nil)
	if err != nil {
		return nil, err
	}

	combinedReqOpts := twitchclient.MergeReqOpts(reqOpts, twitchclient.ReqOpts{
		StatName:       "service.tmi.get_channel_banned_words",
		StatSampleRate: defaultStatSampleRate,
	})
	resp, err := c.Do(ctx, req, combinedReqOpts)
	if err != nil {
		return nil, err
	}

	defer closeBody(resp)

	if resp.StatusCode != http.StatusOK {
		return nil, httpErrorImpl{
			statusCode: resp.StatusCode,
		}
	}

	decoded := &ChannelBannedWordsMessage{}
	if err := json.NewDecoder(resp.Body).Decode(decoded); err != nil {
		return nil, err
	}
	return decoded, nil
}

func (c *clientImpl) AddChannelBannedWord(ctx context.Context, channelID string, requesterUserID string, msg AddChannelBannedWordParams, reqOpts *twitchclient.ReqOpts) (*AddChannelBlockedTermResponse, error) {
	path := fmt.Sprintf("/rooms/%s/banned_word/%s", url.QueryEscape(channelID), url.QueryEscape(requesterUserID))
	bodyBytes, err := json.Marshal(msg)
	if err != nil {
		return nil, err
	}
	req, err := c.NewRequest("POST", path, bytes.NewReader(bodyBytes))
	if err != nil {
		return nil, err
	}

	combinedReqOpts := twitchclient.MergeReqOpts(reqOpts, twitchclient.ReqOpts{
		StatName:       "service.clue.add_channel_banned_word",
		StatSampleRate: defaultStatSampleRate,
	})
	resp, err := c.Do(ctx, req, combinedReqOpts)
	if err != nil {
		return nil, err
	}

	defer closeBody(resp)

	decoded := AddChannelBlockedTermResponse{}
	if err := json.NewDecoder(resp.Body).Decode(&decoded); err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return &AddChannelBlockedTermResponse{
			ResponseCode: decoded.ResponseCode,
		}, twitchclient.HandleFailedResponse(resp)
	}
	return &AddChannelBlockedTermResponse{
		ResponseCode: decoded.ResponseCode,
		Phrases:      msg.Word.Phrases,
		AddedAt:      time.Now().UTC(),
	}, nil
}

func (c *clientImpl) DeleteChannelBannedWord(ctx context.Context, channelID string, requesterUserID string, msg DeleteChannelBannedWordParams, reqOpts *twitchclient.ReqOpts) (*DeleteChannelBlockedTermResponse, error) {
	path := fmt.Sprintf("/rooms/%s/banned_word/%s", url.QueryEscape(channelID), url.QueryEscape(requesterUserID))
	bodyBytes, err := json.Marshal(msg)
	if err != nil {
		return nil, err
	}
	req, err := c.NewRequest("DELETE", path, bytes.NewReader(bodyBytes))
	if err != nil {
		return nil, err
	}

	combinedReqOpts := twitchclient.MergeReqOpts(reqOpts, twitchclient.ReqOpts{
		StatName:       "service.clue.delete_channel_banned_word",
		StatSampleRate: defaultStatSampleRate,
	})
	resp, err := c.Do(ctx, req, combinedReqOpts)
	if err != nil {
		return nil, err
	}

	defer closeBody(resp)

	decoded := DeleteChannelBlockedTermResponse{}
	if err := json.NewDecoder(resp.Body).Decode(&decoded); err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return &DeleteChannelBlockedTermResponse{
			ResponseCode: decoded.ResponseCode,
		}, twitchclient.HandleFailedResponse(resp)
	}
	return &DeleteChannelBlockedTermResponse{
		ResponseCode: decoded.ResponseCode,
		Phrases:      msg.Phrases,
		DeletedAt:    time.Now().UTC(),
	}, nil
}

func (c *clientImpl) ChannelPermittedWords(ctx context.Context, channelID, requesterUserID string, reqOpts *twitchclient.ReqOpts) (*ChannelPermittedWordsMessage, error) {
	path := fmt.Sprintf("/rooms/%s/permitted_words/%s", url.QueryEscape(channelID), url.QueryEscape(requesterUserID))
	req, err := c.NewRequest("GET", path, nil)
	if err != nil {
		return nil, err
	}

	combinedReqOpts := twitchclient.MergeReqOpts(reqOpts, twitchclient.ReqOpts{
		StatName:       "service.tmi.get_channel_permitted_words",
		StatSampleRate: defaultStatSampleRate,
	})
	resp, err := c.Do(ctx, req, combinedReqOpts)
	if err != nil {
		return nil, err
	}

	defer closeBody(resp)

	if resp.StatusCode != http.StatusOK {
		return nil, httpErrorImpl{
			statusCode: resp.StatusCode,
		}
	}

	decoded := &ChannelPermittedWordsMessage{}
	if err := json.NewDecoder(resp.Body).Decode(decoded); err != nil {
		return nil, err
	}
	return decoded, nil
}

func (c *clientImpl) AddChannelPermittedWord(ctx context.Context, channelID, requesterUserID string, msg AddChannelPermittedWordParams, reqOpts *twitchclient.ReqOpts) (*AddChannelPermittedTermResponse, error) {
	path := fmt.Sprintf("/rooms/%s/permitted_word/%s", url.QueryEscape(channelID), url.QueryEscape(requesterUserID))
	bodyBytes, err := json.Marshal(msg)
	if err != nil {
		return nil, err
	}
	req, err := c.NewRequest("POST", path, bytes.NewReader(bodyBytes))
	if err != nil {
		return nil, err
	}

	combinedReqOpts := twitchclient.MergeReqOpts(reqOpts, twitchclient.ReqOpts{
		StatName:       "service.clue.add_channel_permitted_word",
		StatSampleRate: defaultStatSampleRate,
	})
	resp, err := c.Do(ctx, req, combinedReqOpts)
	if err != nil {
		return nil, err
	}

	defer closeBody(resp)

	decoded := AddChannelPermittedTermResponse{}
	if err := json.NewDecoder(resp.Body).Decode(&decoded); err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return &AddChannelPermittedTermResponse{
			ResponseCode: decoded.ResponseCode,
		}, twitchclient.HandleFailedResponse(resp)
	}
	return &AddChannelPermittedTermResponse{
		ResponseCode: decoded.ResponseCode,
		Phrases:      msg.Word.Phrases,
		AddedAt:      time.Now().UTC(),
	}, nil
}

func (c *clientImpl) DeleteChannelPermittedWord(ctx context.Context, channelID string, requesterUserID string, msg DeleteChannelPermittedWordParams, reqOpts *twitchclient.ReqOpts) (*DeleteChannelPermittedTermResponse, error) {
	path := fmt.Sprintf("/rooms/%s/permitted_word/%s", url.QueryEscape(channelID), url.QueryEscape(requesterUserID))
	bodyBytes, err := json.Marshal(msg)
	if err != nil {
		return nil, err
	}
	req, err := c.NewRequest("DELETE", path, bytes.NewReader(bodyBytes))
	if err != nil {
		return nil, err
	}

	combinedReqOpts := twitchclient.MergeReqOpts(reqOpts, twitchclient.ReqOpts{
		StatName:       "service.clue.delete_channel_permitted_word",
		StatSampleRate: defaultStatSampleRate,
	})
	resp, err := c.Do(ctx, req, combinedReqOpts)
	if err != nil {
		return nil, err
	}

	defer closeBody(resp)

	decoded := DeleteChannelPermittedTermResponse{}
	if err := json.NewDecoder(resp.Body).Decode(&decoded); err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		return &DeleteChannelPermittedTermResponse{
			ResponseCode: decoded.ResponseCode,
		}, twitchclient.HandleFailedResponse(resp)
	}
	return &DeleteChannelPermittedTermResponse{
		ResponseCode: decoded.ResponseCode,
		Phrases:      msg.Phrases,
		DeletedAt:    time.Now().UTC(),
	}, nil
}
