package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/accessruleaudit"
	"github.com/jackc/pgx/v5/pgxpool"
)

func main() {
	if len(os.Args) < 2 {
		usage()
		os.Exit(64)
	}
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()
	var err error
	switch os.Args[1] {
	case "export":
		err = runExport(ctx, os.Args[2:])
	case "verify":
		err = runVerify(ctx, os.Args[2:])
	default:
		usage()
		os.Exit(64)
	}
	if err != nil {
		fmt.Fprintln(os.Stderr, "access-rule audit:", err)
		os.Exit(1)
	}
}

func usage() {
	fmt.Fprintln(os.Stderr, `Usage:
  easyai-ai-gateway-access-rule-audit export --output <before.json>
  easyai-ai-gateway-access-rule-audit verify --before <before.json> --output <after.json>

Both commands are database read-only and only emit grouped counts and SHA-256
digests. Use a SELECT-only AI_GATEWAY_ACCESS_RULE_AUDIT_DATABASE_URL role.`)
}

func runExport(ctx context.Context, args []string) error {
	flags := flag.NewFlagSet("export", flag.ContinueOnError)
	output := flags.String("output", "", "secret-safe access-rule audit output")
	if err := flags.Parse(args); err != nil {
		return err
	}
	if flags.NArg() != 0 {
		return errors.New("unexpected export arguments")
	}
	snapshot, outputPath, err := exportSnapshot(ctx, *output)
	if err != nil {
		return err
	}
	if err := writeSnapshot(outputPath, snapshot); err != nil {
		return err
	}
	fmt.Printf("access_rule_audit_export=PASS total=%d allow=%d deny=%d live_sha256=%s\n", snapshot.Live.Total, snapshot.Live.AllowCount, snapshot.Live.DenyCount, snapshot.Live.SHA256)
	return nil
}

func runVerify(ctx context.Context, args []string) error {
	flags := flag.NewFlagSet("verify", flag.ContinueOnError)
	beforePathValue := flags.String("before", "", "pre-migration audit snapshot")
	output := flags.String("output", "", "post-migration audit output")
	if err := flags.Parse(args); err != nil {
		return err
	}
	if flags.NArg() != 0 {
		return errors.New("unexpected verify arguments")
	}
	beforePath, err := regularInputPath(*beforePathValue)
	if err != nil {
		return err
	}
	after, outputPath, err := exportSnapshot(ctx, *output)
	if err != nil {
		return err
	}
	if beforePath == outputPath {
		return errors.New("verification output must not overwrite the pre-migration snapshot")
	}
	payload, err := os.ReadFile(beforePath)
	if err != nil {
		return err
	}
	before, err := accessruleaudit.Decode(payload)
	if err != nil {
		return err
	}
	if err := accessruleaudit.VerifyMigration(before, after); err != nil {
		return err
	}
	if err := writeSnapshot(outputPath, after); err != nil {
		return err
	}
	fmt.Printf("access_rule_audit_verify=PASS archived_allow=%d live_allow=%d live_deny=%d deny_sha256=%s\n", after.Archive.ArchivedAllowCount, after.Live.AllowCount, after.Live.DenyCount, after.Live.DenySHA256)
	return nil
}

func exportSnapshot(ctx context.Context, output string) (accessruleaudit.Snapshot, string, error) {
	databaseURL := strings.TrimSpace(os.Getenv("AI_GATEWAY_ACCESS_RULE_AUDIT_DATABASE_URL"))
	if databaseURL == "" {
		return accessruleaudit.Snapshot{}, "", errors.New("AI_GATEWAY_ACCESS_RULE_AUDIT_DATABASE_URL is required")
	}
	outputPath, err := safeOutputPath(output)
	if err != nil {
		return accessruleaudit.Snapshot{}, "", err
	}
	pool, err := pgxpool.New(ctx, databaseURL)
	if err != nil {
		return accessruleaudit.Snapshot{}, "", err
	}
	defer pool.Close()
	snapshot, err := accessruleaudit.Export(ctx, pool, time.Now())
	return snapshot, outputPath, err
}

func writeSnapshot(path string, snapshot accessruleaudit.Snapshot) error {
	payload, err := accessruleaudit.Encode(snapshot)
	if err != nil {
		return err
	}
	return os.WriteFile(path, payload, 0o600)
}

func regularInputPath(path string) (string, error) {
	path = strings.TrimSpace(path)
	if path == "" {
		return "", errors.New("input path is required")
	}
	absolute, err := filepath.Abs(path)
	if err != nil {
		return "", err
	}
	info, err := os.Lstat(absolute)
	if err != nil {
		return "", err
	}
	if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
		return "", errors.New("input must be a regular non-symlink file")
	}
	return absolute, nil
}

func safeOutputPath(path string) (string, error) {
	path = strings.TrimSpace(path)
	if path == "" {
		return "", errors.New("output path is required")
	}
	absolute, err := filepath.Abs(path)
	if err != nil {
		return "", err
	}
	parent := filepath.Dir(absolute)
	if err := os.MkdirAll(parent, 0o700); err != nil {
		return "", err
	}
	if info, err := os.Lstat(absolute); err == nil {
		if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
			return "", errors.New("output must be a regular non-symlink file")
		}
	} else if !os.IsNotExist(err) {
		return "", err
	}
	return absolute, nil
}
