// Copyright 2024 James Hatfield
// SPDX-License-Identifier: MIT

//go:build ignore

package main

import (
	"bufio"
	"bytes"
	"crypto"
	"flag"
	"fmt"
	"go/format"
	"io"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
)

const disposableEmailListURL string = "https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/%s/disposable_email_blocklist.conf"

var (
	gitRef *string = flag.String("r", "master", "Git reference of the domain list version")
	outPat *string = flag.String("o", "modules/setting/disposable_email_domain_data.go", "Output path")
	check  *bool   = flag.Bool("check", false, "Check if the current output file matches the current upstream list")
)

func main() {
	flag.Parse()

	if *check {
		// read in the local copy of the domain list
		local, err := get_local_file()
		if err != nil {
			log.Fatalf("File Read Error: %v", err)
		}

		// generate the remote copy of the domain list
		remote, err := generate()
		if err != nil {
			log.Fatalf("Generation Error: %v", err)
		}

		// strip the comments from both (so we dont fail simply due to git ref difference)
		local = strip_comments(local)
		remote = strip_comments(remote)

		// generate the hash of the local copy
		local_sha, err := hash(local)
		if err != nil {
			log.Fatalf("Local Hash Generation Error: %v", err)
		}

		// generate the hash of the remote copy
		remote_sha, err := hash(remote)
		if err != nil {
			log.Fatalf("Remote Hash Generation Error: %v", err)
		}

		// if the hashes dont match then the local copy needs to be updated
		if local_sha != remote_sha {
			log.Fatalf("Disposable email domain list needs to be updated!! \"make lint-disposable-emails-fix\"")
		}
	} else {
		// generate the source code (array of domains)
		res, err := generate()
		if err != nil {
			log.Fatalf("Generation Error: %v", err)
		}

		// write result to a file
		err = os.WriteFile(*outPat, res, 0o644)
		if err != nil {
			log.Fatalf("File Write Error: %v", err)
		}
	}
}

func strip_comments(data []byte) []byte {
	result := make([]byte, 0, len(data))

	re := regexp.MustCompile(`^\W*//.*$`)

	for _, line := range bytes.Split(data, []byte("\n")) {
		if !re.Match(line) {
			result = append(result, line...)
		}
	}

	return result
}

func hash(data []byte) (string, error) {
	var err error

	hash := crypto.SHA3_256.New()

	_, err = hash.Write(data)
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("%x", hash.Sum(nil)), err
}

func get_local_file() ([]byte, error) {
	var err error

	f, err := os.Open(*outPat)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	data, err := io.ReadAll(f)
	if err != nil {
		return nil, err
	}

	return data, err
}

func get_remote() ([]string, error) {
	var err error
	var url string = fmt.Sprintf(disposableEmailListURL, *gitRef)

	// download the domain list
	res, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	body, err := io.ReadAll(res.Body)
	if err != nil {
		return nil, err
	}

	// go through all entries (1 domain per line)
	scanner := bufio.NewScanner(bytes.NewReader(body))

	var arrDomains []string
	for scanner.Scan() {
		line := scanner.Text()
		arrDomains = append(arrDomains, line)
	}

	return arrDomains, err
}

func generate() ([]byte, error) {
	var err error
	var url string = fmt.Sprintf(disposableEmailListURL, *gitRef)

	// download the domains list
	arrDomains, err := get_remote()
	if err != nil {
		return nil, err
	}

	// build the string in a readable way
	var sb strings.Builder

	_, err = sb.WriteString("[]string{\n")
	if err != nil {
		return nil, err
	}

	for _, item := range arrDomains {
		_, err = sb.WriteString(fmt.Sprintf("\t%q,\n", item))
		if err != nil {
			return nil, err
		}
	}

	_, err = sb.WriteString("}")
	if err != nil {
		return nil, err
	}

	// insert the values into file
	final := fmt.Sprintf(hdr, url, sb.String())

	return format.Source([]byte(final))
}

const hdr = `
// Copyright 2024 James Hatfield
// SPDX-License-Identifier: MIT
//
// Code generated by build/generate-disposable-email.go. DO NOT EDIT
// Sourced from %s
package setting

import "sync"

var DisposableEmailDomains = sync.OnceValue(func() []string {
		return %s
})
`