package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"strings"

	"golang.org/x/xerrors"
)

const header = `// Code generated by gen/enum. DO NOT EDIT.
package database
`

func main() {
	if err := run(); err != nil {
		panic(err)
	}
}

func run() error {
	dump, err := os.Open("dump.sql")
	if err != nil {
		_, _ = fmt.Fprintf(os.Stderr, "error: %s must be run in the database directory with dump.sql present\n", os.Args[0])
		return err
	}
	defer dump.Close()

	var uniqueConstraints []string

	s := bufio.NewScanner(dump)
	query := ""
	for s.Scan() {
		line := strings.TrimSpace(s.Text())
		switch {
		case strings.HasPrefix(line, "--"):
		case line == "":
		case strings.HasSuffix(line, ";"):
			query += line
			if isUniqueConstraint(query) {
				uniqueConstraints = append(uniqueConstraints, query)
			}
			query = ""
		default:
			query += line + " "
		}
	}
	if err = s.Err(); err != nil {
		return err
	}

	return writeContents("unique_constraint.go", uniqueConstraints, generateUniqueConstraints)
}

func isUniqueConstraint(query string) bool {
	return strings.Contains(query, "UNIQUE")
}

func generateUniqueConstraints(queries []string) ([]byte, error) {
	s := &bytes.Buffer{}

	_, _ = fmt.Fprint(s, header)
	_, _ = fmt.Fprint(s, `
// UniqueConstraint represents a named unique constraint on a table.
type UniqueConstraint string

// UniqueConstraint enums.
const (
`)
	for _, query := range queries {
		name := ""
		switch {
		case strings.Contains(query, "ALTER TABLE") && strings.Contains(query, "ADD CONSTRAINT"):
			name = strings.Split(query, " ")[6]
		case strings.Contains(query, "CREATE UNIQUE INDEX"):
			name = strings.Split(query, " ")[3]
		default:
			return nil, xerrors.Errorf("unknown unique constraint format: %s", query)
		}
		_, _ = fmt.Fprintf(s, "\tUnique%s UniqueConstraint = %q // %s\n", nameFromSnakeCase(name), name, query)
	}
	_, _ = fmt.Fprint(s, ")\n")

	return s.Bytes(), nil
}

func writeContents[T any](dest string, arg T, fn func(T) ([]byte, error)) error {
	b, err := fn(arg)
	if err != nil {
		return err
	}
	err = os.WriteFile(dest, b, 0o600)
	if err != nil {
		return err
	}
	cmd := exec.Command("go", "run", "golang.org/x/tools/cmd/goimports@latest", "-w", dest)
	return cmd.Run()
}

func nameFromSnakeCase(s string) string {
	var ret string
	for _, ss := range strings.Split(s, "_") {
		switch ss {
		case "id":
			ret += "ID"
		case "ids":
			ret += "IDs"
		case "jwt":
			ret += "JWT"
		case "idx":
			ret += "Index"
		default:
			ret += strings.Title(ss)
		}
	}
	return ret
}
