package commands

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

// openemr_nginx.go — per-install nginx hardening for OpenEMR (GH #631).
// OpenEMR serves from the install root, but several sub-trees under it must
// never be web-served: sites/*/documents + sites/*/edi + sites/*/era hold
// PHI (patient documents, billing files), and sql/, contrib/, and the
// various *.inc/config files are code/setup surfaces. These are denied with
// = / ^~ locations that beat the vhost's `~ \.php$`, so e.g. a request for
// /sites/default/documents/... or /contrib/util/installScripts/InstallerAuto.php
// returns 404 instead of leaking records or re-triggering the installer.

func openemrSnippetPath(domain, subdir string) string {
	slug := subdir
	if slug == "" {
		slug = "root"
	}
	return filepath.Join(nginxJabaliDir, domain, "openemr-"+slug+".conf")
}

func openemrNginxConf(osUser, subdir string) string {
	p := ""
	if subdir != "" {
		p = "/" + subdir
	}
	var b strings.Builder
	b.WriteString("# Auto-generated by jabali-agent. Do not edit.\n")
	b.WriteString("# OpenEMR PHI + setup-path hardening (GH #631).\n")
	// PHI + code sub-trees: deny wholesale.
	for _, d := range []string{
		"/sites/default/documents/", "/sites/default/edi/", "/sites/default/era/",
		"/sql/", "/contrib/", "/tests/", "/.git", "/vendor/bin/",
	} {
		fmt.Fprintf(&b, "location ^~ %s%s { deny all; return 404; }\n", p, d)
	}
	// Setup + config files that must never be re-run / read over the web.
	for _, f := range []string{
		"/contrib/util/installScripts/InstallerAuto.php",
		"/sites/default/sqlconf.php", "/sites/default/config.php",
		"/composer.json", "/composer.lock",
	} {
		fmt.Fprintf(&b, "location = %s%s { deny all; return 404; }\n", p, f)
	}
	if subdir != "" {
		fmt.Fprintf(&b, "location ^~ %s/ {\n", p)
		b.WriteString("    location ~ \\.php$ {\n")
		fmt.Fprintf(&b, "        fastcgi_pass unix:/run/php/jabali-%s/fpm.sock;\n", osUser)
		b.WriteString("        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;\n")
		b.WriteString("        include fastcgi_params;\n")
		b.WriteString("    }\n")
		fmt.Fprintf(&b, "    try_files $uri $uri/ %s/index.php?$query_string;\n", p)
		b.WriteString("}\n")
	}
	return b.String()
}

func writeOpenEMRNginx(ctx context.Context, domain, osUser, subdir string) error {
	if !domainRegex.MatchString(domain) {
		return fmt.Errorf("invalid domain %q", domain)
	}
	if subdir != "" && !subdirSlugRE.MatchString(subdir) {
		return fmt.Errorf("invalid subdir slug %q", subdir)
	}
	if !osUserSafeRE.MatchString(osUser) {
		return fmt.Errorf("invalid os_user %q", osUser)
	}
	domainDir := filepath.Join(nginxJabaliDir, domain)
	if err := os.MkdirAll(domainDir, 0o755); err != nil {
		return fmt.Errorf("mkdir %s: %w", domainDir, err)
	}
	dest := openemrSnippetPath(domain, subdir)
	if err := os.WriteFile(dest, []byte(openemrNginxConf(osUser, subdir)), 0o644); err != nil {
		return fmt.Errorf("write %s: %w", dest, err)
	}
	return reloadNginxAfterSnippet(ctx, dest)
}

func removeOpenEMRNginx(ctx context.Context, domain, subdir string) error {
	if !domainRegex.MatchString(domain) {
		return fmt.Errorf("invalid domain %q", domain)
	}
	// Validate subdir before it reaches the snippet FILENAME — an
	// unvalidated "../.." would let filepath.Join escape the domain dir and
	// os.Remove (root) an arbitrary .conf.
	if subdir != "" && !subdirSlugRE.MatchString(subdir) {
		return fmt.Errorf("invalid subdir slug %q", subdir)
	}
	dest := openemrSnippetPath(domain, subdir)
	if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("remove %s: %w", dest, err)
	}
	return reloadNginxAfterSnippet(ctx, dest)
}
