package main

import (
	"encoding/json"
	"log"
	"net/http"
	"os"
	"strings"
)

// some headers not worth logging
var (
	hdrsToNotLog = []string{
		"Accept-Language",
		"Cache-Control",
		"Cf-Ray",
		"CF-Visitor",
		"CF-Connecting-IP",
		"Cdn-Loop",
		"Cookie",
		"Connection",
		"Dnt",
		"If-Modified-Since",
		"Sec-Fetch-Dest",
		"Sec-Ch-Ua-Mobile",
		// "Sec-Ch-Ua",
		"Sec-Ch-Ua-Platform",
		"Sec-Fetch-Site",
		"Sec-Fetch-Mode",
		"Sec-Fetch-User",
		"Upgrade-Insecure-Requests",
		"X-Request-Start",
		"X-Forwarded-For",
		"X-Forwarded-Proto",
		"X-Forwarded-Host",
	}
	hdrsToNotLogMap map[string]bool
)

type LogFile struct {
	handle *os.File
	logger *log.Logger
	path   string
}

type LogFileRec struct {
	Method string `json:"method"`
	IpAddr string `json:"ipAddr"`
	Url    string `json:"url"`
}

func newFileLogger(path string) (*LogFile, error) {
	f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		return nil, err
	}
	return &LogFile{
		handle: f,
		logger: log.New(f, "", 0),
		path:   path,
	}, nil
}

func (f *LogFile) Close() error {
	if f == nil {
		return nil
	}
	err := f.handle.Close()
	f.handle = nil
	return err
}

func extractFirstFragment(header *http.Header, headerName string) string {
	s := header.Get(headerName)
	if len(strings.TrimSpace(s)) == 0 {
		return s
	}
	fragments := strings.Split(s, ",")
	return strings.TrimSpace(fragments[0])
}

// Get Ip Address of the client
func extractIpAddress(r *http.Request) string {
	var ipAddr string
	if r == nil {
		return ""
	}
	possibleIpHeaders := []string{"CF-Connecting-IP", "X-Real-Ip", "X-Forwarded-For"}
	for _, header := range possibleIpHeaders {
		ipAddr = extractFirstFragment(&r.Header, header)
		if len(strings.TrimSpace(ipAddr)) != 0 {
			return ipAddr
		}
	}
	// pull ip from Request.RemoteAddr
	if len(strings.TrimSpace(r.RemoteAddr)) != 0 {
		index := strings.LastIndex(r.RemoteAddr, ";")
		if index == -1 {
			return r.RemoteAddr
		}
		ipAddr = r.RemoteAddr[:index]
	}
	return ipAddr
}

func canSkipExtraHeaders(r *http.Request) bool {
	ref := r.Header.Get("Referer")
	if len(strings.TrimSpace(ref)) == 0 {
		return false
	}
	return strings.Contains(ref, r.Host)
}

func shouldLogHeader(s string) bool {
	if hdrsToNotLogMap == nil {
		hdrsToNotLogMap = map[string]bool{}
		for _, h := range hdrsToNotLog {
			h = strings.ToLower(h)
			hdrsToNotLogMap[h] = true
		}
	}
	s = strings.ToLower(s)
	return !hdrsToNotLogMap[s]
}

func (f *LogFile) WriteLog(r *http.Request) error {
	if f == nil {
		return nil
	}
	var rec = make(map[string]string)
	rec["method"] = r.Method
	rec["requestUri"] = r.RequestURI
	rec["Host"] = r.Host
	rec["ipAddr"] = extractIpAddress(r)
	if !canSkipExtraHeaders(r) {
		for key, val := range r.Header {
			if shouldLogHeader(key) && len(val) > 0 {
				rec[key] = val[0]
			}
		}
	}
	b, err := json.Marshal(rec)
	if err != nil {
		return err
	}
	f.logger.Println(string(b))
	return nil
}