// Copyright 2017 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package clog

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
)

type slackAttachment struct {
	Text  string `json:"text"`
	Color string `json:"color"`
}

type slackPayload struct {
	Attachments []slackAttachment `json:"attachments"`
}

var slackColors = []string{
	"",        // Trace
	"#3aa3e3", // Info
	"warning", // Warn
	"danger",  // Error
	"#ff0200", // Fatal
}

type SlackConfig struct {
	// Minimum level of messages to be processed.
	Level LEVEL
	// Buffer size defines how many messages can be queued before hangs.
	BufferSize int64
	// Slack webhook URL.
	URL string
}

type slack struct {
	Adapter

	url string
}

func newSlack() Logger {
	return &slack{
		Adapter: Adapter{
			quitChan: make(chan struct{}),
		},
	}
}

func (s *slack) Level() LEVEL { return s.level }

func (s *slack) Init(v interface{}) error {
	cfg, ok := v.(SlackConfig)
	if !ok {
		return ErrConfigObject{"SlackConfig", v}
	}

	if !isValidLevel(cfg.Level) {
		return ErrInvalidLevel{}
	}
	s.level = cfg.Level

	if len(cfg.URL) == 0 {
		return errors.New("URL cannot be empty")
	}
	s.url = cfg.URL

	s.msgChan = make(chan *Message, cfg.BufferSize)
	return nil
}

func (s *slack) ExchangeChans(errorChan chan<- error) chan *Message {
	s.errorChan = errorChan
	return s.msgChan
}

func buildSlackPayload(msg *Message) (string, error) {
	payload := slackPayload{
		Attachments: []slackAttachment{
			{
				Text:  msg.Body,
				Color: slackColors[msg.Level],
			},
		},
	}
	p, err := json.Marshal(&payload)
	if err != nil {
		return "", err
	}
	return string(p), nil
}

func (s *slack) write(msg *Message) {
	payload, err := buildSlackPayload(msg)
	if err != nil {
		s.errorChan <- fmt.Errorf("slack: buildSlackPayload: %v", err)
		return
	}

	resp, err := http.Post(s.url, "application/json", bytes.NewReader([]byte(payload)))
	if err != nil {
		s.errorChan <- fmt.Errorf("slack: %v", err)
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode/100 != 2 {
		data, _ := ioutil.ReadAll(resp.Body)
		s.errorChan <- fmt.Errorf("slack: %s", data)
	}
}

func (s *slack) Start() {
LOOP:
	for {
		select {
		case msg := <-s.msgChan:
			s.write(msg)
		case <-s.quitChan:
			break LOOP
		}
	}

	for {
		if len(s.msgChan) == 0 {
			break
		}

		s.write(<-s.msgChan)
	}
	s.quitChan <- struct{}{} // Notify the cleanup is done.
}

func (s *slack) Destroy() {
	s.quitChan <- struct{}{}
	<-s.quitChan

	close(s.msgChan)
	close(s.quitChan)
}

func init() {
	Register(SLACK, newSlack)
}