package main

import (
	"bytes"
	"embed"
	"encoding/json"
	"html/template"
	"log/slog"
	"net"
	"net/http"
	"net/url"
	"os"
	"strings"

	"github.com/BurntSushi/toml"
)

//go:embed countries.toml
var countriesTOML []byte

func main() {
	log := slog.New(slog.NewTextHandler(os.Stderr, nil))

	socketPath := os.Getenv("SOCKET_PATH")
	if socketPath == "" {
		log.Error("SOCKET_PATH must be set")
		os.Exit(1)
	}

	domain := os.Getenv("DOMAIN")
	if domain == "" {
		log.Error("DOMAIN environment variable must be set")
		os.Exit(1)
	}

	var handler = handler{logger: log, domain: domain, lawsByCC: map[string]map[string]law{}}
	meta, err := toml.NewDecoder(bytes.NewReader(countriesTOML)).Decode(&handler.countries)
	if err != nil {
		log.Error("failed to parse countries.toml", Error(err))
		os.Exit(1)
	}
	if len(meta.Undecoded()) != 0 {
		log.Error("unknown keys in countries.toml", "keys", meta.Undecoded())
		os.Exit(1)
	}

	lawFiles, err := os.ReadDir("laws")
	if err != nil {
		log.Error("failed to read laws/ directory", Error(err))
		os.Exit(1)
	}
	for _, file := range lawFiles {
		text, err := os.ReadFile("laws/" + file.Name())
		if err != nil {
			log.Error("failed to read file", Error(err), "path", "laws/"+file.Name())
			os.Exit(1)
		}
		var laws []law
		err = json.Unmarshal([]byte(text), &laws)
		if err != nil {
			log.Error("failed to parse file", Error(err), "path", file.Name())
			os.Exit(1)
		}
		cc := strings.SplitN(file.Name(), ".", 2)[0]
		handler.lawsByCC[cc] = map[string]law{}
		for _, law := range laws {
			if law.Redir != "" {
				handler.lawsByCC[cc][law.Redir] = law
			}
		}
	}
	http.HandleFunc("/", handler.handle)
	listener, err := net.Listen("unix", socketPath)
	if err != nil {
		log.Error("failed to listen", Error(err))
		os.Exit(1)
	}
	if err := os.Chmod(socketPath, 0666); err != nil {
		log.Error("failed to set socket permissions", Error(err), "path", socketPath)
		return
	}
	err = http.Serve(listener, nil)
	log.Error("failed to serve", Error(err))
}

//go:embed templates
var templates embed.FS

var tpl, _ = template.New("").Funcs(template.FuncMap{
	"ToUpper": strings.ToUpper,
}).Option("missingkey=error").ParseFS(templates, "templates/*")

type handler struct {
	logger    *slog.Logger
	domain    string
	countries map[string]country
	lawsByCC  map[string]map[string]law
}

func (h *handler) handle(w http.ResponseWriter, r *http.Request) {
	if r.Host == h.domain {
		if r.URL.Path != "/" {
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte("page not found"))
			return
		}
		var html bytes.Buffer
		err := tpl.ExecuteTemplate(&html, "index.html.tmpl", map[string]any{
			"Countries": h.countries,
			"Domain":    r.Host,
		})
		if err != nil {
			h.logger.Error("failed to execute index template", Error(err))
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}
		w.Write(html.Bytes())
		return
	}

	cc, isSubdomain := strings.CutSuffix(r.Host, "."+h.domain)
	if !isSubdomain {
		h.logger.Error("request for unknown hosts", "host", r.Host)
		w.Write([]byte("unknown host"))
		return
	}
	key := strings.TrimLeft(r.URL.Path, "/")
	if len(key) > 0 {
		val, ok := h.lawsByCC[cc][key]
		if !ok {
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte("unknown law"))
			return
		}
		http.Redirect(w, r, val.URL, 302)
	} else {
		query := r.URL.Query().Get("q")
		if query != "" {
			country, ok := h.countries[cc]
			if !ok {
				w.WriteHeader(http.StatusNotFound)
				w.Write([]byte("search not implemented for this country"))
				return
			}
			if country.HasPlaceholder() {
				http.Redirect(w, r, strings.Replace(country.SearchURL, "%s", url.QueryEscape(query), 1), 302)
				return
			} else {
				w.WriteHeader(http.StatusBadRequest)
			}
		}
		_, hasJSONLaws := h.lawsByCC[cc]
		var html bytes.Buffer
		err := tpl.ExecuteTemplate(&html, "search.html.tmpl", map[string]any{
			"TLD":         cc,
			"Domain":      h.domain,
			"Country":     h.countries[cc],
			"HasJSONLaws": hasJSONLaws,
		})
		if err != nil {
			h.logger.Error("failed to execute search template", Error(err))
			http.Error(w, "internal server error", http.StatusInternalServerError)
			return
		}
		w.Write(html.Bytes())
	}
}

type law struct {
	URL   string
	Title string
	Abbr  string
	Redir string
}

type country struct {
	Name      string
	SearchURL string `toml:"search-url"`
}

func (c country) HasPlaceholder() bool {
	return strings.Contains(c.SearchURL, "%s")
}

func Error(value error) slog.Attr { return slog.String("error", value.Error()) }