package rfc3164

import (
	"errors"
	"time"

	"github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/syslog/internal/parser/utils"
)

type RFC3164Option func(*RFC3164)

type RFC3164 struct {
	PRI       int
	Timestamp time.Time
	Hostname  string
	Tag       string
	Message   string
	PID       string
	//
	len            int
	position       int
	buf            []byte
	useCurrentYear bool //If no year is specified in the timestamp, use the current year
	strictHostname bool //If the hostname contains invalid characters or is not an IP, return an error
}

const PRI_MAX_LEN = 3

//Order is important: format with the most information must be first because we will stop on the first match
var VALID_TIMESTAMPS = []string{
	time.RFC3339,
	"Jan 02 15:04:05 2006",
	"Jan _2 15:04:05 2006",
	"Jan 02 15:04:05",
	"Jan _2 15:04:05",
}

func WithCurrentYear() RFC3164Option {
	return func(r *RFC3164) {
		r.useCurrentYear = true
	}
}

func WithStrictHostname() RFC3164Option {
	return func(r *RFC3164) {
		r.strictHostname = true
	}
}

func (r *RFC3164) parsePRI() error {
	pri := 0

	if r.buf[r.position] != '<' {
		return errors.New("PRI must start with '<'")
	}

	r.position++

	for r.position < r.len {
		c := r.buf[r.position]
		if c == '>' {
			r.position++
			break
		}
		if c < '0' || c > '9' {
			return errors.New("PRI must be a number")
		}
		pri = pri*10 + int(c-'0')
		r.position++
	}

	if pri > 999 {
		return errors.New("PRI must be up to 3 characters long")
	}

	if r.position == r.len && r.buf[r.position-1] != '>' {
		return errors.New("PRI must end with '>'")
	}

	r.PRI = pri
	return nil
}

func (r *RFC3164) parseTimestamp() error {
	validTs := false
	for _, layout := range VALID_TIMESTAMPS {
		tsLen := len(layout)
		if r.position+tsLen > r.len {
			continue
		}
		t, err := time.Parse(layout, string(r.buf[r.position:r.position+tsLen]))
		if err == nil {
			validTs = true
			r.Timestamp = t
			r.position += tsLen
			break
		}
	}
	if !validTs {
		return errors.New("timestamp is not valid")
	}
	if r.useCurrentYear {
		if r.Timestamp.Year() == 0 {
			r.Timestamp = time.Date(time.Now().Year(), r.Timestamp.Month(), r.Timestamp.Day(), r.Timestamp.Hour(), r.Timestamp.Minute(), r.Timestamp.Second(), r.Timestamp.Nanosecond(), r.Timestamp.Location())
		}
	}
	r.position++
	return nil
}

func (r *RFC3164) parseHostname() error {
	hostname := []byte{}
	for r.position < r.len {
		c := r.buf[r.position]
		if c == ' ' {
			r.position++
			break
		}
		hostname = append(hostname, c)
		r.position++
	}
	if r.strictHostname {
		if !utils.IsValidHostnameOrIP(string(hostname)) {
			return errors.New("hostname is not valid")
		}
	}
	if len(hostname) == 0 {
		return errors.New("hostname is empty")
	}
	r.Hostname = string(hostname)
	return nil
}

//We do not enforce tag len as quite a lot of syslog client send tags with more than 32 chars
func (r *RFC3164) parseTag() error {
	tag := []byte{}
	tmpPid := []byte{}
	pidEnd := false
	hasPid := false
	for r.position < r.len {
		c := r.buf[r.position]
		if !utils.IsAlphaNumeric(c) {
			break
		}
		tag = append(tag, c)
		r.position++
	}
	if len(tag) == 0 {
		return errors.New("tag is empty")
	}
	r.Tag = string(tag)

	if r.position == r.len {
		return nil
	}

	c := r.buf[r.position]
	if c == '[' {
		hasPid = true
		r.position++
		for r.position < r.len {
			c = r.buf[r.position]
			if c == ']' {
				pidEnd = true
				r.position++
				break
			}
			if c < '0' || c > '9' {
				return errors.New("pid inside tag must be a number")
			}
			tmpPid = append(tmpPid, c)
			r.position++
		}
	}

	if hasPid && !pidEnd {
		return errors.New("pid inside tag must be closed with ']'")
	}

	if hasPid {
		r.PID = string(tmpPid)
	}
	return nil
}

func (r *RFC3164) parseMessage() error {
	err := r.parseTag()
	if err != nil {
		return err
	}

	if r.position == r.len {
		return errors.New("message is empty")
	}

	c := r.buf[r.position]

	if c == ':' {
		r.position++
	}

	for {
		if r.position >= r.len {
			return errors.New("message is empty")
		}
		c := r.buf[r.position]
		if c != ' ' {
			break
		}
		r.position++
	}

	message := r.buf[r.position:r.len]
	r.Message = string(message)
	return nil
}

func (r *RFC3164) Parse(message []byte) error {
	r.len = len(message)
	if r.len == 0 {
		return errors.New("message is empty")
	}
	r.buf = message

	err := r.parsePRI()
	if err != nil {
		return err
	}

	err = r.parseTimestamp()
	if err != nil {
		return err
	}

	err = r.parseHostname()
	if err != nil {
		return err
	}

	err = r.parseMessage()
	if err != nil {
		return err
	}

	return nil
}

func NewRFC3164Parser(opts ...RFC3164Option) *RFC3164 {
	r := &RFC3164{}
	for _, opt := range opts {
		opt(r)
	}
	return r
}
