// SPDX-License-Identifier: MIT

package config

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"strings"
)

// Env holds all variables needed to generate the .env file on the remote server.
// Used for the MCP-only stack.
type Env struct {
	NextcloudURL      string
	NextcloudUser     string
	NextcloudPassword string
	Domain            string
	AuthSecret        string
	ClientSecret      string
	// Pre-filled static values
	AuthIssuer          string
	ClientID            string
	RegistrationEnabled bool
	RegistrationToken   string
	// CrowdSec + TLS
	CrowdSecBouncerKey string
	AcmeEmail          string
	// Monitoring (Grafana)
	GrafanaPassword string
}

// NCEnv holds all variables needed for the Nextcloud-only stack .env file.
type NCEnv struct {
	NCDomain           string
	NCAdminUser        string
	NCAdminPassword    string
	PostgresPassword   string
	CrowdSecBouncerKey string
	AcmeEmail          string
	NCMetricsToken     string
}

// FullEnv holds all variables needed for the full (Nextcloud + MCP) stack .env file.
type FullEnv struct {
	NCDomain           string
	MCPDomain          string
	NCAdminUser        string
	NCAdminPassword    string
	NCMCPUser          string
	NCMCPPassword      string
	PostgresPassword   string
	AuthSecret         string
	ClientSecret       string
	AuthIssuer         string
	ClientID           string
	RegistrationEnabled bool
	RegistrationToken  string
	MCPInternalToken   string
	CrowdSecBouncerKey string
	AcmeEmail          string
	NCMetricsToken     string
}

// Generate builds the .env file content with random secrets.
func Generate(ncURL, ncUser, ncPassword, domain, acmeEmail string) (*Env, error) {
	authSecret, err := randomHex(32)
	if err != nil {
		return nil, fmt.Errorf("generate auth secret: %w", err)
	}
	clientSecret, err := randomHex(32)
	if err != nil {
		return nil, fmt.Errorf("generate client secret: %w", err)
	}
	regToken, err := randomHex(16)
	if err != nil {
		return nil, fmt.Errorf("generate registration token: %w", err)
	}
	bouncerKey, err := randomHex(32)
	if err != nil {
		return nil, fmt.Errorf("generate crowdsec bouncer key: %w", err)
	}
	grafanaPassword, err := randomHex(16)
	if err != nil {
		return nil, fmt.Errorf("generate grafana password: %w", err)
	}

	if !strings.HasPrefix(ncURL, "http://") && !strings.HasPrefix(ncURL, "https://") {
		ncURL = "https://" + ncURL
	}

	return &Env{
		NextcloudURL:        ncURL,
		NextcloudUser:       ncUser,
		NextcloudPassword:   ncPassword,
		Domain:              domain,
		AuthSecret:          authSecret,
		ClientSecret:        clientSecret,
		AuthIssuer:          "https://" + domain,
		ClientID:            "",
		RegistrationEnabled: true,
		RegistrationToken:   regToken,
		CrowdSecBouncerKey:  bouncerKey,
		AcmeEmail:           acmeEmail,
		GrafanaPassword:     grafanaPassword,
	}, nil
}

// Render returns the contents of the .env file as a string.
func (e *Env) Render() string {
	var b strings.Builder
	b.WriteString("# AIquila MCP Server — generated by aiquila-hetzner\n\n")

	b.WriteString("# Nextcloud connection\n")
	b.WriteString(fmt.Sprintf("NEXTCLOUD_URL=%s\n", e.NextcloudURL))
	b.WriteString(fmt.Sprintf("NEXTCLOUD_USER=%s\n", e.NextcloudUser))
	b.WriteString(fmt.Sprintf("NEXTCLOUD_PASSWORD=%s\n", e.NextcloudPassword))
	b.WriteString("\n")

	b.WriteString("# MCP transport\n")
	b.WriteString("MCP_TRANSPORT=http\n")
	b.WriteString("MCP_PORT=3339\n")
	b.WriteString("\n")

	b.WriteString("# OAuth 2.0\n")
	b.WriteString("MCP_AUTH_ENABLED=true\n")
	b.WriteString(fmt.Sprintf("MCP_AUTH_SECRET=%s\n", e.AuthSecret))
	b.WriteString(fmt.Sprintf("MCP_AUTH_ISSUER=%s\n", e.AuthIssuer))
	b.WriteString(fmt.Sprintf("MCP_CLIENT_ID=%s\n", e.ClientID))
	b.WriteString(fmt.Sprintf("MCP_CLIENT_SECRET=%s\n", e.ClientSecret))
	b.WriteString("MCP_CLIENT_REDIRECT_URIS=\n")
	b.WriteString(fmt.Sprintf("MCP_REGISTRATION_ENABLED=%v\n", e.RegistrationEnabled))
	b.WriteString(fmt.Sprintf("MCP_REGISTRATION_TOKEN=%s\n", e.RegistrationToken))
	b.WriteString("MCP_TRUST_PROXY=true\n")
	b.WriteString("\n")

	b.WriteString("# Traefik / TLS\n")
	b.WriteString(fmt.Sprintf("MCP_DOMAIN=%s\n", e.Domain))
	b.WriteString(fmt.Sprintf("ACME_EMAIL=%s\n", e.AcmeEmail))
	b.WriteString("\n")

	b.WriteString("# CrowdSec\n")
	b.WriteString(fmt.Sprintf("CROWDSEC_BOUNCER_KEY=%s\n", e.CrowdSecBouncerKey))
	b.WriteString("\n")

	b.WriteString("# Monitoring (Grafana)\n")
	b.WriteString(fmt.Sprintf("GRAFANA_PASSWORD=%s\n", e.GrafanaPassword))
	b.WriteString("\n")

	b.WriteString("# Logging\n")
	b.WriteString("LOG_LEVEL=info\n")

	return b.String()
}

// GenerateNC builds the .env file content for the Nextcloud-only stack with random secrets.
func GenerateNC(ncDomain, ncAdminUser, ncAdminPassword, acmeEmail string) (*NCEnv, error) {
	postgresPassword, err := randomHex(32)
	if err != nil {
		return nil, fmt.Errorf("generate postgres password: %w", err)
	}
	bouncerKey, err := randomHex(32)
	if err != nil {
		return nil, fmt.Errorf("generate crowdsec bouncer key: %w", err)
	}
	metricsToken, err := randomHex(16)
	if err != nil {
		return nil, fmt.Errorf("generate metrics token: %w", err)
	}
	return &NCEnv{
		NCDomain:           ncDomain,
		NCAdminUser:        ncAdminUser,
		NCAdminPassword:    ncAdminPassword,
		PostgresPassword:   postgresPassword,
		CrowdSecBouncerKey: bouncerKey,
		AcmeEmail:          acmeEmail,
		NCMetricsToken:     metricsToken,
	}, nil
}

// Render returns the contents of the Nextcloud-only .env file as a string.
func (e *NCEnv) Render() string {
	var b strings.Builder
	b.WriteString("# AIquila Nextcloud — generated by aiquila-hetzner\n\n")

	b.WriteString("# Domain & TLS\n")
	b.WriteString(fmt.Sprintf("NC_DOMAIN=%s\n", e.NCDomain))
	b.WriteString(fmt.Sprintf("ACME_EMAIL=%s\n", e.AcmeEmail))
	b.WriteString("\n")

	b.WriteString("# Nextcloud admin account\n")
	b.WriteString(fmt.Sprintf("NC_ADMIN_USER=%s\n", e.NCAdminUser))
	b.WriteString(fmt.Sprintf("NC_ADMIN_PASSWORD=%s\n", e.NCAdminPassword))
	b.WriteString("\n")

	b.WriteString("# PostgreSQL\n")
	b.WriteString(fmt.Sprintf("POSTGRES_PASSWORD=%s\n", e.PostgresPassword))
	b.WriteString("\n")

	b.WriteString("# CrowdSec\n")
	b.WriteString(fmt.Sprintf("CROWDSEC_BOUNCER_KEY=%s\n", e.CrowdSecBouncerKey))
	b.WriteString("\n")

	b.WriteString("# NC33 metrics app — endpoint: https://<NC_DOMAIN>/metrics?token=<value>\n")
	b.WriteString(fmt.Sprintf("NC_METRICS_TOKEN=%s\n", e.NCMetricsToken))

	return b.String()
}

// GenerateFull builds the .env file content for the full (NC+MCP) stack with random secrets.
// ncMCPUser and ncMCPPassword are the Nextcloud credentials the MCP server will use;
// they are typically set after NC is up and an app password has been generated via OCC.
func GenerateFull(ncDomain, mcpDomain, ncAdminUser, ncAdminPassword, ncMCPUser, ncMCPPassword, acmeEmail string) (*FullEnv, error) {
	postgresPassword, err := randomHex(32)
	if err != nil {
		return nil, fmt.Errorf("generate postgres password: %w", err)
	}
	authSecret, err := randomHex(32)
	if err != nil {
		return nil, fmt.Errorf("generate auth secret: %w", err)
	}
	clientSecret, err := randomHex(32)
	if err != nil {
		return nil, fmt.Errorf("generate client secret: %w", err)
	}
	regToken, err := randomHex(16)
	if err != nil {
		return nil, fmt.Errorf("generate registration token: %w", err)
	}
	bouncerKey, err := randomHex(32)
	if err != nil {
		return nil, fmt.Errorf("generate crowdsec bouncer key: %w", err)
	}
	metricsToken, err := randomHex(16)
	if err != nil {
		return nil, fmt.Errorf("generate metrics token: %w", err)
	}
	internalToken, err := randomHex(32)
	if err != nil {
		return nil, fmt.Errorf("generate internal token: %w", err)
	}
	return &FullEnv{
		NCDomain:            ncDomain,
		MCPDomain:           mcpDomain,
		NCAdminUser:         ncAdminUser,
		NCAdminPassword:     ncAdminPassword,
		NCMCPUser:           ncMCPUser,
		NCMCPPassword:       ncMCPPassword,
		PostgresPassword:    postgresPassword,
		AuthSecret:          authSecret,
		ClientSecret:        clientSecret,
		AuthIssuer:          "https://" + mcpDomain,
		ClientID:            "",
		RegistrationEnabled: true,
		RegistrationToken:   regToken,
		MCPInternalToken:    internalToken,
		CrowdSecBouncerKey:  bouncerKey,
		AcmeEmail:           acmeEmail,
		NCMetricsToken:      metricsToken,
	}, nil
}

// Render returns the contents of the full stack .env file as a string.
func (e *FullEnv) Render() string {
	var b strings.Builder
	b.WriteString("# AIquila Full Stack — generated by aiquila-hetzner\n\n")

	b.WriteString("# Domains & TLS\n")
	b.WriteString(fmt.Sprintf("NC_DOMAIN=%s\n", e.NCDomain))
	b.WriteString(fmt.Sprintf("MCP_DOMAIN=%s\n", e.MCPDomain))
	b.WriteString(fmt.Sprintf("ACME_EMAIL=%s\n", e.AcmeEmail))
	b.WriteString("\n")

	b.WriteString("# Nextcloud admin account\n")
	b.WriteString(fmt.Sprintf("NC_ADMIN_USER=%s\n", e.NCAdminUser))
	b.WriteString(fmt.Sprintf("NC_ADMIN_PASSWORD=%s\n", e.NCAdminPassword))
	b.WriteString("\n")

	b.WriteString("# Nextcloud user for MCP (app password)\n")
	b.WriteString(fmt.Sprintf("NC_MCP_USER=%s\n", e.NCMCPUser))
	b.WriteString(fmt.Sprintf("NC_MCP_PASSWORD=%s\n", e.NCMCPPassword))
	b.WriteString("\n")

	b.WriteString("# PostgreSQL\n")
	b.WriteString(fmt.Sprintf("POSTGRES_PASSWORD=%s\n", e.PostgresPassword))
	b.WriteString("\n")

	b.WriteString("# OAuth 2.0\n")
	b.WriteString("MCP_AUTH_ENABLED=true\n")
	b.WriteString(fmt.Sprintf("MCP_AUTH_SECRET=%s\n", e.AuthSecret))
	b.WriteString(fmt.Sprintf("MCP_AUTH_ISSUER=%s\n", e.AuthIssuer))
	b.WriteString(fmt.Sprintf("MCP_CLIENT_ID=%s\n", e.ClientID))
	b.WriteString(fmt.Sprintf("MCP_CLIENT_SECRET=%s\n", e.ClientSecret))
	b.WriteString("MCP_CLIENT_REDIRECT_URIS=\n")
	b.WriteString(fmt.Sprintf("MCP_REGISTRATION_ENABLED=%v\n", e.RegistrationEnabled))
	b.WriteString(fmt.Sprintf("MCP_REGISTRATION_TOKEN=%s\n", e.RegistrationToken))
	b.WriteString("MCP_TRUST_PROXY=true\n")
	b.WriteString("\n")

	b.WriteString("# Extra hostnames for MCP SDK DNS rebinding protection (Docker service names)\n")
	b.WriteString("MCP_ALLOWED_HOSTS=mcp\n")
	b.WriteString("\n")
	b.WriteString("# Internal token for NC→MCP auth on Docker network\n")
	b.WriteString(fmt.Sprintf("MCP_INTERNAL_TOKEN=%s\n", e.MCPInternalToken))
	b.WriteString("\n")

	b.WriteString("# CrowdSec\n")
	b.WriteString(fmt.Sprintf("CROWDSEC_BOUNCER_KEY=%s\n", e.CrowdSecBouncerKey))
	b.WriteString("\n")

	b.WriteString("# NC33 metrics app — endpoint: https://<NC_DOMAIN>/metrics?token=<value>\n")
	b.WriteString(fmt.Sprintf("NC_METRICS_TOKEN=%s\n", e.NCMetricsToken))
	b.WriteString("\n")

	b.WriteString("# Logging\n")
	b.WriteString("LOG_LEVEL=info\n")

	return b.String()
}

func randomHex(n int) (string, error) {
	b := make([]byte, n)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return hex.EncodeToString(b), nil
}
