package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// структура для парсинга json, который приходит в запросе
type ClientInfo struct {
	ClientID   string                 `json:"clid"`
	CounterID  int                    `json:"cid"`
	UserParams map[string]interface{} `json:"uprm"`
}

func main() {
	pipeProcessor(processInput)
}

func processHit(nginxDate, payload string) ([]map[string]string, error) {
	var err error
	var result []map[string]string

	payload, err = strconv.Unquote(fmt.Sprintf(`"%s"`, payload))
	if err != nil {
		return result, err
	}
	if payload == "" {
		return result, err
	}

	ClientInfo := &ClientInfo{}
	_ = json.Unmarshal([]byte(payload), &ClientInfo)

	// достанем параметры пользователя
	// если их ноль, то дальше не продолжаем
	userParams := ClientInfo.UserParams
	numberOfUserParams := len(userParams)
	if numberOfUserParams == 0 {
		return result, nil
	}

	// подговтовим ClientID, CounterID и дату
	clid := ClientInfo.ClientID
	cid := ClientInfo.CounterID
	d := nginxDate[:10]
	dt := d + " " + nginxDate[11:19]

	// получим map без вложенных map'ов
	userFlatParams := getFlatUserParams(userParams, "")
	// сформируем слайс из map'ов, где каждый map отражает строку для таблицы metrika.users_params
	for k, v := range userFlatParams {
		clientInfo := map[string]string{
			"Key":          k,
			"Value":        v,
			"ClientID":     clid,
			"CounterID":    strconv.Itoa(cid),
			"LastDate":     d,
			"LastDateTime": dt,
		}
		result = append(result, clientInfo)
	}

	return result, nil
}

func processInput(line string) ([]string, error) {
	var out []string
	in := strings.Split(line, "\t")

	if len(in) < 3 {
		return out, nil
	}

	hits, err := processHit(in[0], in[2])
	if err != nil {
		return out, err
	}

	// сформируем слайс строк, где каждая строка в формате JSONEachRow(для вставки в таблицу metrika.users_params)
	var s []byte
	for _, hit := range hits {
		s, err = json.Marshal(hit)
		if err != nil {
			return out, err
		}
		out = append(out, string(s))
	}

	return out, nil
}

func pipeProcessor(f func(string) ([]string, error)) {
	scanner := bufio.NewScanner(os.Stdin)
	writer := bufio.NewWriter(os.Stdout)

	var s []string
	var err error
	for scanner.Scan() {
		s, err = f(scanner.Text())
		if err != nil {
			fmt.Fprintln(os.Stderr, "Error:", err)
			continue
		}
		for _, v := range s {
			writer.WriteString(v)
			writer.WriteString("\n")
			writer.Flush()
		}
	}

	if err := scanner.Err(); err != nil {
		fmt.Fprintln(os.Stderr, "reading standard input:", err)
	}
}

// функция для развертывния вложенных мапов
// пример:
// исходный мап {"LoginID": "123456789","exv":{"status": "vip"}}
// на выходе - {"LoginID": "123456789", "exv.status": "vip"}}
func getFlatUserParams(m map[string]interface{}, prefix string) map[string]string {
	flatMap := make(map[string]string)
	// ограничение по вложенности
	// если больше пяти - дальше не смотрим
	if strings.Count(prefix, ".") > 4 {
		return flatMap
	}
	for k, v := range m {
		switch v := v.(type) {
		case string:
			flatMap[prefix+k] = v
		case float64:
			flatMap[prefix+k] = strconv.Itoa(int(v))
		case map[string]interface{}:
			extraflatMap := getFlatUserParams(v, prefix+k+".")
			for k1, v1 := range extraflatMap {
				flatMap[k1] = v1
			}
		default:
			flatMap[prefix+k] = ""
		}
	}
	return flatMap
}
