package naive

import (
	"context"
	_ "embed"
	"fmt"
	"log/slog"
	"math/rand/v2"
	"net/http"
	"net/netip"
	"time"

	"github.com/TecharoHQ/anubis/internal"
	"github.com/TecharoHQ/anubis/internal/honeypot"
	"github.com/TecharoHQ/anubis/lib/policy/checker"
	"github.com/TecharoHQ/anubis/lib/store"
	"github.com/a-h/templ"
	"github.com/google/uuid"
	"github.com/nikandfor/spintax"
)

//go:generate go tool github.com/a-h/templ/cmd/templ generate

// XXX(Xe): All of this was generated by ChatGPT, GLM 4.6, and GPT-OSS 120b. This is pseudoprofound bullshit in spintax[1] format so that the bullshit generator can emit plausibly human-authored text while being very computationally cheap.
//
// It feels somewhat poetic to use spammer technology in Anubis.
//
// [1]: https://outboundly.ai/blogs/what-is-spintax-and-how-to-use-it/
//
//go:embed spintext.txt
var spintext string

//go:embed titles.txt
var titles string

//go:embed affirmations.txt
var affirmations string

func New(st store.Interface, lg *slog.Logger) (*Impl, error) {
	affirmation, err := spintax.Parse(affirmations)
	if err != nil {
		return nil, fmt.Errorf("can't parse affirmations: %w", err)
	}

	body, err := spintax.Parse(spintext)
	if err != nil {
		return nil, fmt.Errorf("can't parse bodies: %w", err)
	}

	title, err := spintax.Parse(titles)
	if err != nil {
		return nil, fmt.Errorf("can't parse titles: %w", err)
	}

	lg.Debug("initialized basic bullshit generator", "affirmations", affirmation.Count(), "bodies", body.Count(), "titles", title.Count())

	return &Impl{
		st:            st,
		infos:         store.JSON[honeypot.Info]{Underlying: st, Prefix: "honeypot:info"},
		uaWeight:      store.JSON[int]{Underlying: st, Prefix: "honeypot:user-agent"},
		networkWeight: store.JSON[int]{Underlying: st, Prefix: "honeypot:network"},
		affirmation:   affirmation,
		body:          body,
		title:         title,
		lg:            lg.With("component", "honeypot/naive"),
	}, nil
}

type Impl struct {
	st            store.Interface
	infos         store.JSON[honeypot.Info]
	uaWeight      store.JSON[int]
	networkWeight store.JSON[int]
	lg            *slog.Logger

	affirmation, body, title spintax.Spintax
}

func (i *Impl) incrementUA(ctx context.Context, userAgent string) int {
	result, _ := i.uaWeight.Get(ctx, internal.SHA256sum(userAgent))
	result++
	i.uaWeight.Set(ctx, internal.SHA256sum(userAgent), result, time.Hour)
	return result
}

func (i *Impl) incrementNetwork(ctx context.Context, network string) int {
	result, _ := i.networkWeight.Get(ctx, internal.SHA256sum(network))
	result++
	i.networkWeight.Set(ctx, internal.SHA256sum(network), result, time.Hour)
	return result
}

func (i *Impl) CheckUA() checker.Impl {
	return checker.Func(func(r *http.Request) (bool, error) {
		result, _ := i.uaWeight.Get(r.Context(), internal.SHA256sum(r.UserAgent()))
		if result >= 25 {
			return true, nil
		}

		return false, nil
	})
}

func (i *Impl) CheckNetwork() checker.Impl {
	return checker.Func(func(r *http.Request) (bool, error) {
		result, _ := i.uaWeight.Get(r.Context(), internal.SHA256sum(r.UserAgent()))
		if result >= 25 {
			return true, nil
		}

		return false, nil
	})
}

func (i *Impl) Hash() string {
	return internal.SHA256sum("naive honeypot")
}

func (i *Impl) makeAffirmations() []string {
	count := rand.IntN(5) + 1

	var result []string
	for j := 0; j < count; j++ {
		result = append(result, i.affirmation.Spin())
	}

	return result
}

func (i *Impl) makeSpins() []string {
	count := rand.IntN(5) + 1

	var result []string
	for j := 0; j < count; j++ {
		result = append(result, i.body.Spin())
	}

	return result
}

func (i *Impl) makeTitle() string {
	return i.title.Spin()
}

func (i *Impl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	t0 := time.Now()
	lg := internal.GetRequestLogger(i.lg, r)

	id := r.PathValue("id")
	if id == "" {
		id = uuid.NewString()
	}

	realIP, _ := internal.RealIP(r)
	if !realIP.IsValid() {
		realIP = netip.MustParseAddr(r.Header.Get("X-Real-Ip"))
	}

	network, ok := internal.ClampIP(realIP)
	if !ok {
		lg.Error("clampIP failed", "output", network, "ok", ok)
		http.Error(w, "The cake is a lie", http.StatusTeapot)
		return
	}

	networkCount := i.incrementNetwork(r.Context(), network.String())
	uaCount := i.incrementUA(r.Context(), r.UserAgent())

	stage := r.PathValue("stage")

	if stage == "init" {
		lg.Debug("found new entrance point", "id", id, "stage", stage, "userAgent", r.UserAgent(), "clampedIP", network)
	} else {
		switch {
		case networkCount%256 == 0, uaCount%256 == 0:
			lg.Warn("found possible crawler", "id", id, "network", network)
		}
	}

	spins := i.makeSpins()
	affirmations := i.makeAffirmations()
	title := i.makeTitle()

	var links []link
	for _, affirmation := range affirmations {
		links = append(links, link{
			href: uuid.NewString(),
			body: affirmation,
		})
	}

	templ.Handler(
		base(title, i.maze(spins, links)),
		templ.WithStreaming(),
		templ.WithStatus(http.StatusOK),
	).ServeHTTP(w, r)

	t1 := time.Since(t0)
	honeypot.Timings.WithLabelValues("naive").Observe(float64(t1.Milliseconds()))
}

type link struct {
	href string
	body string
}
