package security

import (
	"fmt"
	"reflect"
	"regexp"
)

var validSQLIdentifier = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)

// SQLNames defines all configurable SQL stored procedure and table names
// used by the security package. Override individual fields to remap
// to custom database objects. Use DefaultSQLNames() for baseline defaults,
// and MergeSQLNames() to apply partial overrides.
type SQLNames struct {
	// Auth procedures (DatabaseAuthenticator)
	Login         string // default: "resolvespec_login"
	Register      string // default: "resolvespec_register"
	Logout        string // default: "resolvespec_logout"
	Session       string // default: "resolvespec_session"
	SessionUpdate string // default: "resolvespec_session_update"
	RefreshToken  string // default: "resolvespec_refresh_token"

	// JWT procedures (JWTAuthenticator)
	JWTLogin  string // default: "resolvespec_jwt_login"
	JWTLogout string // default: "resolvespec_jwt_logout"

	// Security policy procedures
	ColumnSecurity string // default: "resolvespec_column_security"
	RowSecurity    string // default: "resolvespec_row_security"

	// TOTP procedures (DatabaseTwoFactorProvider)
	TOTPEnable             string // default: "resolvespec_totp_enable"
	TOTPDisable            string // default: "resolvespec_totp_disable"
	TOTPGetStatus          string // default: "resolvespec_totp_get_status"
	TOTPGetSecret          string // default: "resolvespec_totp_get_secret"
	TOTPRegenerateBackup   string // default: "resolvespec_totp_regenerate_backup_codes"
	TOTPValidateBackupCode string // default: "resolvespec_totp_validate_backup_code"

	// Passkey procedures (DatabasePasskeyProvider)
	PasskeyStoreCredential    string // default: "resolvespec_passkey_store_credential"
	PasskeyGetCredsByUsername string // default: "resolvespec_passkey_get_credentials_by_username"
	PasskeyGetCredential      string // default: "resolvespec_passkey_get_credential"
	PasskeyUpdateCounter      string // default: "resolvespec_passkey_update_counter"
	PasskeyGetUserCredentials string // default: "resolvespec_passkey_get_user_credentials"
	PasskeyDeleteCredential   string // default: "resolvespec_passkey_delete_credential"
	PasskeyUpdateName         string // default: "resolvespec_passkey_update_name"
	PasskeyLogin              string // default: "resolvespec_passkey_login"

	// Password reset procedures (DatabaseAuthenticator)
	PasswordResetRequest  string // default: "resolvespec_password_reset_request"
	PasswordResetComplete string // default: "resolvespec_password_reset"

	// OAuth2 procedures (DatabaseAuthenticator OAuth2 methods)
	OAuthGetOrCreateUser    string // default: "resolvespec_oauth_getorcreateuser"
	OAuthCreateSession      string // default: "resolvespec_oauth_createsession"
	OAuthGetRefreshToken    string // default: "resolvespec_oauth_getrefreshtoken"
	OAuthUpdateRefreshToken string // default: "resolvespec_oauth_updaterefreshtoken"
	OAuthGetUser            string // default: "resolvespec_oauth_getuser"

	// OAuth2 server procedures (OAuthServer persistence)
	OAuthRegisterClient string // default: "resolvespec_oauth_register_client"
	OAuthGetClient      string // default: "resolvespec_oauth_get_client"
	OAuthSaveCode       string // default: "resolvespec_oauth_save_code"
	OAuthExchangeCode   string // default: "resolvespec_oauth_exchange_code"
	OAuthIntrospect     string // default: "resolvespec_oauth_introspect"
	OAuthRevoke         string // default: "resolvespec_oauth_revoke"
}

// DefaultSQLNames returns an SQLNames with all default resolvespec_* values.
func DefaultSQLNames() *SQLNames {
	return &SQLNames{
		Login:         "resolvespec_login",
		Register:      "resolvespec_register",
		Logout:        "resolvespec_logout",
		Session:       "resolvespec_session",
		SessionUpdate: "resolvespec_session_update",
		RefreshToken:  "resolvespec_refresh_token",

		JWTLogin:  "resolvespec_jwt_login",
		JWTLogout: "resolvespec_jwt_logout",

		ColumnSecurity: "resolvespec_column_security",
		RowSecurity:    "resolvespec_row_security",

		TOTPEnable:             "resolvespec_totp_enable",
		TOTPDisable:            "resolvespec_totp_disable",
		TOTPGetStatus:          "resolvespec_totp_get_status",
		TOTPGetSecret:          "resolvespec_totp_get_secret",
		TOTPRegenerateBackup:   "resolvespec_totp_regenerate_backup_codes",
		TOTPValidateBackupCode: "resolvespec_totp_validate_backup_code",

		PasskeyStoreCredential:    "resolvespec_passkey_store_credential",
		PasskeyGetCredsByUsername: "resolvespec_passkey_get_credentials_by_username",
		PasskeyGetCredential:      "resolvespec_passkey_get_credential",
		PasskeyUpdateCounter:      "resolvespec_passkey_update_counter",
		PasskeyGetUserCredentials: "resolvespec_passkey_get_user_credentials",
		PasskeyDeleteCredential:   "resolvespec_passkey_delete_credential",
		PasskeyUpdateName:         "resolvespec_passkey_update_name",
		PasskeyLogin:              "resolvespec_passkey_login",

		PasswordResetRequest:  "resolvespec_password_reset_request",
		PasswordResetComplete: "resolvespec_password_reset",

		OAuthGetOrCreateUser:    "resolvespec_oauth_getorcreateuser",
		OAuthCreateSession:      "resolvespec_oauth_createsession",
		OAuthGetRefreshToken:    "resolvespec_oauth_getrefreshtoken",
		OAuthUpdateRefreshToken: "resolvespec_oauth_updaterefreshtoken",
		OAuthGetUser:            "resolvespec_oauth_getuser",

		OAuthRegisterClient: "resolvespec_oauth_register_client",
		OAuthGetClient:      "resolvespec_oauth_get_client",
		OAuthSaveCode:       "resolvespec_oauth_save_code",
		OAuthExchangeCode:   "resolvespec_oauth_exchange_code",
		OAuthIntrospect:     "resolvespec_oauth_introspect",
		OAuthRevoke:         "resolvespec_oauth_revoke",
	}
}

// MergeSQLNames returns a copy of base with any non-empty fields from override applied.
// If override is nil, a copy of base is returned.
func MergeSQLNames(base, override *SQLNames) *SQLNames {
	if override == nil {
		copied := *base
		return &copied
	}
	merged := *base
	if override.Login != "" {
		merged.Login = override.Login
	}
	if override.Register != "" {
		merged.Register = override.Register
	}
	if override.Logout != "" {
		merged.Logout = override.Logout
	}
	if override.Session != "" {
		merged.Session = override.Session
	}
	if override.SessionUpdate != "" {
		merged.SessionUpdate = override.SessionUpdate
	}
	if override.RefreshToken != "" {
		merged.RefreshToken = override.RefreshToken
	}
	if override.JWTLogin != "" {
		merged.JWTLogin = override.JWTLogin
	}
	if override.JWTLogout != "" {
		merged.JWTLogout = override.JWTLogout
	}
	if override.ColumnSecurity != "" {
		merged.ColumnSecurity = override.ColumnSecurity
	}
	if override.RowSecurity != "" {
		merged.RowSecurity = override.RowSecurity
	}
	if override.TOTPEnable != "" {
		merged.TOTPEnable = override.TOTPEnable
	}
	if override.TOTPDisable != "" {
		merged.TOTPDisable = override.TOTPDisable
	}
	if override.TOTPGetStatus != "" {
		merged.TOTPGetStatus = override.TOTPGetStatus
	}
	if override.TOTPGetSecret != "" {
		merged.TOTPGetSecret = override.TOTPGetSecret
	}
	if override.TOTPRegenerateBackup != "" {
		merged.TOTPRegenerateBackup = override.TOTPRegenerateBackup
	}
	if override.TOTPValidateBackupCode != "" {
		merged.TOTPValidateBackupCode = override.TOTPValidateBackupCode
	}
	if override.PasskeyStoreCredential != "" {
		merged.PasskeyStoreCredential = override.PasskeyStoreCredential
	}
	if override.PasskeyGetCredsByUsername != "" {
		merged.PasskeyGetCredsByUsername = override.PasskeyGetCredsByUsername
	}
	if override.PasskeyGetCredential != "" {
		merged.PasskeyGetCredential = override.PasskeyGetCredential
	}
	if override.PasskeyUpdateCounter != "" {
		merged.PasskeyUpdateCounter = override.PasskeyUpdateCounter
	}
	if override.PasskeyGetUserCredentials != "" {
		merged.PasskeyGetUserCredentials = override.PasskeyGetUserCredentials
	}
	if override.PasskeyDeleteCredential != "" {
		merged.PasskeyDeleteCredential = override.PasskeyDeleteCredential
	}
	if override.PasskeyUpdateName != "" {
		merged.PasskeyUpdateName = override.PasskeyUpdateName
	}
	if override.PasskeyLogin != "" {
		merged.PasskeyLogin = override.PasskeyLogin
	}
	if override.PasswordResetRequest != "" {
		merged.PasswordResetRequest = override.PasswordResetRequest
	}
	if override.PasswordResetComplete != "" {
		merged.PasswordResetComplete = override.PasswordResetComplete
	}
	if override.OAuthGetOrCreateUser != "" {
		merged.OAuthGetOrCreateUser = override.OAuthGetOrCreateUser
	}
	if override.OAuthCreateSession != "" {
		merged.OAuthCreateSession = override.OAuthCreateSession
	}
	if override.OAuthGetRefreshToken != "" {
		merged.OAuthGetRefreshToken = override.OAuthGetRefreshToken
	}
	if override.OAuthUpdateRefreshToken != "" {
		merged.OAuthUpdateRefreshToken = override.OAuthUpdateRefreshToken
	}
	if override.OAuthGetUser != "" {
		merged.OAuthGetUser = override.OAuthGetUser
	}
	if override.OAuthRegisterClient != "" {
		merged.OAuthRegisterClient = override.OAuthRegisterClient
	}
	if override.OAuthGetClient != "" {
		merged.OAuthGetClient = override.OAuthGetClient
	}
	if override.OAuthSaveCode != "" {
		merged.OAuthSaveCode = override.OAuthSaveCode
	}
	if override.OAuthExchangeCode != "" {
		merged.OAuthExchangeCode = override.OAuthExchangeCode
	}
	if override.OAuthIntrospect != "" {
		merged.OAuthIntrospect = override.OAuthIntrospect
	}
	if override.OAuthRevoke != "" {
		merged.OAuthRevoke = override.OAuthRevoke
	}
	return &merged
}

// ValidateSQLNames checks that all non-empty fields in names are valid SQL identifiers.
// Returns an error if any field contains invalid characters.
func ValidateSQLNames(names *SQLNames) error {
	v := reflect.ValueOf(names).Elem()
	typ := v.Type()
	for i := 0; i < v.NumField(); i++ {
		field := v.Field(i)
		if field.Kind() != reflect.String {
			continue
		}
		val := field.String()
		if val != "" && !validSQLIdentifier.MatchString(val) {
			return fmt.Errorf("SQLNames.%s contains invalid characters: %q", typ.Field(i).Name, val)
		}
	}
	return nil
}

// resolveSQLNames merges an optional override with defaults.
// Used by constructors that accept variadic *SQLNames.
func resolveSQLNames(override ...*SQLNames) *SQLNames {
	if len(override) > 0 && override[0] != nil {
		return MergeSQLNames(DefaultSQLNames(), override[0])
	}
	return DefaultSQLNames()
}
