package main

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

	"errors"

	"github.com/sony/sonyflake"
)

var Hitmap []string
var Flake *sonyflake.Sonyflake
var charsReplacer *strings.Replacer

type Payload struct {
	EventType    uint8       `json:"et"`
	Referer      string      `json:"referer"`
	URL          string      `json:"url"`
	Ident        Ident       `json:"ident"`
	ElementName  string      `json:"en"`
	ElementValue string      `json:"ev"`
	FormData     string      `json:"form"`
	SameSite     uint8       `json:"ss"`
	ExternalID   interface{} `json:"eid"`
	ExtraValues  string      `json:"exv"`
	ABParams     string      `json:"abParams"`
}
type Ident struct {
	CounterID uint32 `json:"cid"`
	VisitID   string `json:"vid"`
	ClientID  string `json:"clid"`

	YandexID              string `json:"yid"`
	GoogleID              string `json:"gaid"`
	PaystationVisit       string `json:"pv"`
	CorpsiteVisit         string `json:"cv"`
	PublisherAccountVisit string `json:"pav"`
	DevelopersVisit       string `json:"dv"`
	FirstVisitedProduct   string `json:"fvp"`

	Title                      string `json:"t"`
	PageCharset                string `json:"pc"`
	NetworkType                string `json:"net"`
	ClientTimeZone             int16  `json:"tz"`
	CookieEnabled              uint8  `json:"c"`
	PageLoadTime               uint32 `json:"plt"`
	LargestContentfulPaintTime uint32 `json:"lcp"`
	BrowserLanguage            string `json:"la"`
	JavaEnabled                uint8  `json:"j"`
	NotificationsEnabled       uint8  `json:"ntf"`
	IsIframe                   uint8  `json:"ifr"`
	ScreenColors               uint8  `json:"sc"`
	ScreenHeight               uint16 `json:"sh"`
	ScreenWidth                uint16 `json:"sw"`
	WindowClientHeight         uint16 `json:"wch"`
	WindowClientWidth          uint16 `json:"wcw"`
}

func init() {
	Flake = sonyflake.NewSonyflake(sonyflake.Settings{StartTime: time.Date(2017, 9, 1, 0, 0, 0, 0, time.UTC)})
	charsReplacer = strings.NewReplacer("\t", " ", "\b", " ", "\n", " ", "\r", " ", "\f", "", "\v", "", "\\", "", "\a", "")

	Hitmap = []string{
		"HitID",
		"HitDate",
		"HitDateTime",
		"CounterID",
		"ClientID",
		"XsollaUID",

		"ExternalID",
		"YandexID",
		"GoogleID",

		"VisitID",
		"IpAddress",
		"EventType",
		"URL",
		"Referer",
		"Title",
		"ElementName",
		"SameSite",
		"FormData",
		"PageCharset",
		"NetworkType",
		"ClientTimeZone",
		"CookieEnabled",
		"PageLoadTime",
		"BrowserLanguage",
		"JavaEnabled",
		"NotificationsEnabled",
		"IsIframe",
		"ScreenColors",
		"ScreenHeight",
		"ScreenWidth",
		"WindowClientHeight",
		"WindowClientWidth",
		"UserAgent",

		"UTMSource",
		"UTMMedium",
		"UTMCampaign",
		"UTMContent",
		"UTMTerm",
		"OpenstatService",
		"OpenstatCampaign",
		"OpenstatAd",
		"OpenstatSource",

		"Browser",
		"BrowserMajorVersion",
		"BrowserMinorVersion",
		"OperatingSystem",
		"OperatingSystemMajorVersion",
		"OperatingSystemMinorVersion",
		"OperatingSystemPatchVersion",
		"DeviceBrand",
		"DeviceModel",
		//"IsMobile",

		"RegionCountry",
		"RegionCountryIso",
		"RegionCity",
		"RegionLatitude",
		"RegionLongitude",

		"SocialNetwork",
		"AdvEngine",
		"SearchEngine",
		"SearchPhrase",
		"TrafficSource",
		"HitMsec",
		"ElementValue",
		"PaystationVisit",
		"CorpsiteVisit",
		"PublisherAccountVisit",
		"DevelopersVisit",
		"FirstVisitedProduct",
		"EmailServiceProvider",
		"ExtraValues",
		"ABParams",
		"LargestContentfulPaintTime",
		"ThirdPartyCookieBlocked",
	}
}

func main() {
	pipeProcessor(processInput)
}

func processHit(nginxDate, ipAddress, payload, ua, xsollauid, nginxMsec, isThirdPartyCookieBlocked string) (map[string]string, error) {
	hit := make(map[string]string, 50)

	// var hitID uint64
	hitID, err := Flake.NextID()
	if err != nil {
		return hit, err
	}
	hit["HitID"] = strconv.FormatUint(hitID, 10)

	if len(nginxDate) < 20 {
		err = errors.New("invalid nginx date length")
		return hit, err
	}

	d := nginxDate[:10]
	dt := d + " " + nginxDate[11:19]
	hit["HitDate"] = d
	hit["HitDateTime"] = dt

	if len(nginxMsec) > 0 {
		hit["HitMsec"] = strings.Replace(nginxMsec, ".", "", 1)
	}

	if len(xsollauid) > 10 {
		hit["XsollaUID"] = parseNumberValue(xsollauid)
	}
	hit["ThirdPartyCookieBlocked"] = isThirdPartyCookieBlocked

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

	data := &Payload{}

	// пытаемся декодировать json, ошибку не обрабатываем
	_ = json.Unmarshal([]byte(payload), &data)

	ident(hit, data)

	referer := parseUrlParams(data.Referer)
	URL := parseUrlParams(data.URL)

	utm(hit, URL)
	openstat(hit, URL)

	socialNetwork(hit, referer)
	searchEngine(hit, referer)
	advEngine(hit)
	emailServiceProvider(hit, referer)

	trafficSource(hit, referer, URL)

	useragent(hit, ua)
	maxmind(hit, ipAddress)
	return hit, nil
}

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

	// может быть несколько ip, берем первый
	ip := in[1]
	pos := strings.Index(ip, ",")
	if pos > 0 {
		ip = ip[0:pos]
	}

	// если в логе нет xsollauid - вставляем пустую строку
	if len(in) == 4 {
		in = append(in, "0")
	}

	if len(in) == 5 {
		in = append(in, "")
	}

	// если в логе нет флага блокировки third-party cookie - вставляем 0(не заблокированы)
	if len(in) == 6 {
		in = append(in, "0")
	}

	hit, err := processHit(in[0], ip, in[2], in[3], in[4], in[5], in[6])

	if err != nil {
		return "", err
	}

	var s string
	var out []string
	for _, v := range Hitmap {
		if val, ok := hit[v]; ok {
			if !(val == "0" || val == "") {
				s = charsReplacer.Replace(val)
			} else {
				s = ""
			}
		} else {
			s = ""
		}

		out = append(out, s)
	}

	return strings.Join(out, "\t"), 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
		}
		writer.WriteString(s)
		writer.WriteString("\n")
		writer.Flush()
	}

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