/*
Maddy Mail Server - Composable all-in-one email server.
Copyright © 2019-2020 Max Mazurov <fox.cpp@disroot.org>, Maddy Mail Server contributors

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

package exterrors

import (
	"fmt"

	"github.com/emersion/go-smtp"
)

type EnhancedCode smtp.EnhancedCode

func (ec EnhancedCode) FormatLog() string {
	return fmt.Sprintf("%d.%d.%d", ec[0], ec[1], ec[2])
}

// SMTPError type is a copy of emersion/go-smtp.SMTPError type
// that extends it with Fields method for logging and reporting
// in maddy. It should be used instead of the go-smtp library type for all
// errors.
type SMTPError struct {
	// SMTP status code. Most of these codes are overly generic and are barely
	// useful. Nonetheless, take a look at the 'Associated basic status code'
	// in the SMTP Enhanced Status Codes registry (below), then check RFC 5321
	// (Section 4.3.2) and pick what you like. Stick to 451 and 554 if there are
	// no useful codes.
	Code int

	// Enhanced SMTP status code. If you are unsure, take a look at
	// https://www.iana.org/assignments/smtp-enhanced-status-codes/smtp-enhanced-status-codes.xhtml
	EnhancedCode EnhancedCode

	// Error message that should be returned to the SMTP client.
	// Usually, it should be a short and generic description of the error
	// that excludes any details. Especially, for checks, avoid
	// mentioning the exact policy mechanism used to avoid disclosing the
	// server configuration details. Don't say "DNS error during DMARC check",
	// say "DNS error during policy check". Same goes for network and file I/O
	// errors. ESPECIALLY, don't include any configuration variables or object
	// identifiers in it.
	Message string

	// If the error was generated by a message check
	// this field includes module name.
	CheckName string

	// If the error was generated by a delivery target
	// this field includes module name.
	TargetName string

	// If the error was generated by a message modifier
	// this field includes module name.
	ModifierName string

	// If the error was generated as a result of another
	// error - this field contains the original error object.
	//
	// Err.Error() will be copied into the 'reason' field returned
	// by the Fields method unless a different values is specified
	// using the Reason field below.
	Err error

	// Textual explanation of the actual error reason. Defaults to the
	// Err.Error() value if Err is not nil, empty string otherwise.
	Reason string

	Misc map[string]interface{}
}

func (se *SMTPError) Unwrap() error {
	return se.Err
}

func (se *SMTPError) Fields() map[string]interface{} {
	ctx := make(map[string]interface{}, len(se.Misc)+3)
	for k, v := range se.Misc {
		ctx[k] = v
	}
	ctx["smtp_code"] = se.Code
	ctx["smtp_enchcode"] = se.EnhancedCode
	ctx["smtp_msg"] = se.Message
	if se.CheckName != "" {
		ctx["check"] = se.CheckName
	}
	if se.TargetName != "" {
		ctx["target"] = se.TargetName
	}
	if se.Reason != "" {
		ctx["reason"] = se.Reason
	} else if se.Err != nil {
		ctx["reason"] = se.Err.Error()
	}
	return ctx
}

// Temporary reports whether
func (se *SMTPError) Temporary() bool {
	return se.Code/100 == 4
}

func (se *SMTPError) Error() string {
	if se.Reason != "" {
		return se.Reason
	}
	if se.Err != nil {
		return se.Err.Error()
	}
	return se.Message
}

// SMTPCode is a convenience function that returns one of its arguments
// depending on the result of exterrors.IsTemporary for the specified error
// object.
func SMTPCode(err error, temporaryCode, permanentCode int) int {
	if IsTemporary(err) {
		return temporaryCode
	}
	return permanentCode
}

// SMTPEnchCode is a convenience function changes the first number of the SMTP enhanced
// status code based on the value exterrors.IsTemporary returns for the specified
// error object.
func SMTPEnchCode(err error, code EnhancedCode) EnhancedCode {
	if IsTemporary(err) {
		code[0] = 4
	}
	code[0] = 5
	return code
}
