package main

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"
)

type Ombi struct {
	server, key string
	header      map[string]string
	httpClient  *http.Client
	noFail      bool
}

func newOmbi(server, key string, noFail bool) *Ombi {
	return &Ombi{
		server: server,
		key:    key,
		noFail: noFail,
		httpClient: &http.Client{
			Timeout: 10 * time.Second,
		},
		header: map[string]string{
			"ApiKey": key,
		},
	}
}

func (ombi *Ombi) _getReader(url string, params map[string]string) (string, int, error) {
	if ombi.key == "" {
		return "", 401, fmt.Errorf("No API key provided")
	}
	var req *http.Request
	if params != nil {
		jsonParams, _ := json.Marshal(params)
		req, _ = http.NewRequest("GET", url, bytes.NewBuffer(jsonParams))
	} else {
		req, _ = http.NewRequest("GET", url, nil)
	}
	for name, value := range ombi.header {
		req.Header.Add(name, value)
	}
	resp, err := ombi.httpClient.Do(req)
	defer timeoutHandler("Ombi", ombi.server, ombi.noFail)
	if err != nil || resp.StatusCode != 200 {
		if resp.StatusCode == 401 {
			return "", 401, fmt.Errorf("Invalid API Key")
		}
		return "", resp.StatusCode, err
	}
	defer resp.Body.Close()
	var data io.Reader
	switch resp.Header.Get("Content-Encoding") {
	case "gzip":
		data, _ = gzip.NewReader(resp.Body)
	default:
		data = resp.Body
	}
	buf := new(strings.Builder)
	_, err = io.Copy(buf, data)
	if err != nil {
		return "", 500, err
	}
	return buf.String(), resp.StatusCode, nil
}

func (ombi *Ombi) _post(url string, data map[string]interface{}, response bool) (string, int, error) {
	responseText := ""
	params, _ := json.Marshal(data)
	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(params))
	req.Header.Add("Content-Type", "application/json")
	for name, value := range ombi.header {
		req.Header.Add(name, value)
	}
	resp, err := ombi.httpClient.Do(req)
	defer timeoutHandler("Ombi", ombi.server, ombi.noFail)
	if err != nil || !(resp.StatusCode == 200 || resp.StatusCode == 201) {
		if resp.StatusCode == 401 {
			return "", 401, fmt.Errorf("Invalid API Key")
		}
		return responseText, resp.StatusCode, err
	}
	if response {
		defer resp.Body.Close()
		var out io.Reader
		switch resp.Header.Get("Content-Encoding") {
		case "gzip":
			out, _ = gzip.NewReader(resp.Body)
		default:
			out = resp.Body
		}
		buf := new(strings.Builder)
		_, err = io.Copy(buf, out)
		if err != nil {
			return "", 500, err
		}
		responseText = buf.String()
	}
	return responseText, resp.StatusCode, nil
}

func (ombi *Ombi) userByID(id string) (result map[string]interface{}, code int, err error) {
	resp, code, err := ombi._getReader(fmt.Sprintf("%s/api/v1/Identity/User/%s", ombi.server, id), nil)
	json.Unmarshal([]byte(resp), &result)
	return
}

func (ombi *Ombi) getUsers() (result []map[string]interface{}, code int, err error) {
	resp, code, err := ombi._getReader(fmt.Sprintf("%s/api/v1/Identity/Users", ombi.server), nil)
	json.Unmarshal([]byte(resp), &result)
	return
}

// Strip these from a user when saving as a template.
// We also need to strip userQualityProfiles{"id", "userId"}
var stripFromOmbi = []string{
	"alias",
	"emailAddress",
	"hasLoggedIn",
	"id",
	"lastLoggedIn",
	"password",
	"userName",
}

func (ombi *Ombi) templateByID(id string) (result map[string]interface{}, code int, err error) {
	result, code, err = ombi.userByID(id)
	if err != nil || code != 200 {
		return
	}
	for _, key := range stripFromOmbi {
		if _, ok := result[key]; ok {
			delete(result, key)
		}
	}
	if qp, ok := result["userQualityProfiles"].(map[string]interface{}); ok {
		delete(qp, "id")
		delete(qp, "userId")
		result["userQualityProfiles"] = qp
	}
	return
}

func (ombi *Ombi) newUser(username, password, email string, template map[string]interface{}) ([]string, int, error) {
	url := fmt.Sprintf("%s/api/v1/Identity", ombi.server)
	user := template
	user["userName"] = username
	user["password"] = password
	user["emailAddress"] = email
	resp, code, err := ombi._post(url, user, true)
	var data map[string]interface{}
	json.Unmarshal([]byte(resp), &data)
	if err != nil || code != 200 {
		var lst []string
		if data["errors"] != nil {
			lst = data["errors"].([]string)
		}
		return lst, code, err
	}
	return nil, code, err
}
