package notification

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// MatrixService handles Matrix chat notifications
type MatrixService struct{}

// NewMatrixService creates a new Matrix notification service
func NewMatrixService() *MatrixService {
	return &MatrixService{}
}

// matrixMessagePayload represents the payload for the Matrix Client-Server API
type matrixMessagePayload struct {
	MsgType       string `json:"msgtype"`
	Body          string `json:"body"`
	Format        string `json:"format,omitempty"`
	FormattedBody string `json:"formatted_body,omitempty"`
}

// SendNotification sends a plain message to a Matrix room
func (ms *MatrixService) SendNotification(config *AlertConfiguration, message string) error {
	if config.MatrixHomeserver == "" || config.MatrixRoomID == "" || config.MatrixAccessToken == "" {
		return fmt.Errorf("matrix homeserver, room ID and access token are required")
	}

	// Encode the room ID for use in the URL path
	encodedRoomID := url.PathEscape(config.MatrixRoomID)

	// Use Unix nanoseconds as a unique transaction ID to prevent duplicate messages
	txnID := fmt.Sprintf("%d", time.Now().UnixNano())

	apiURL := fmt.Sprintf("%s/_matrix/client/v3/rooms/%s/send/m.room.message/%s",
		strings.TrimRight(config.MatrixHomeserver, "/"),
		encodedRoomID,
		txnID,
	)

	payload := matrixMessagePayload{
		MsgType: "m.text",
		Body:    message,
	}

	jsonData, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("matrix: failed to marshal payload: %w", err)
	}

	req, err := http.NewRequest(http.MethodPut, apiURL, bytes.NewBuffer(jsonData))
	if err != nil {
		return fmt.Errorf("matrix: failed to create request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+config.MatrixAccessToken)

	client := &http.Client{Timeout: 10 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("matrix: HTTP request failed: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("matrix: API returned status %d", resp.StatusCode)
	}

	return nil
}

// SendServerNotification sends a server-specific notification via Matrix
func (ms *MatrixService) SendServerNotification(config *AlertConfiguration, payload *NotificationPayload, template *ServerNotificationTemplate, resourceType string) error {
	message := ms.generateServerMessage(payload, template, resourceType)
	return ms.SendNotification(config, message)
}

// SendServiceNotification sends a service-specific notification via Matrix
func (ms *MatrixService) SendServiceNotification(config *AlertConfiguration, payload *NotificationPayload, template *ServiceNotificationTemplate) error {
	message := ms.generateServiceMessage(payload, template)
	return ms.SendNotification(config, message)
}

func (ms *MatrixService) generateServerMessage(payload *NotificationPayload, template *ServerNotificationTemplate, resourceType string) string {
	var templateMessage string

	switch strings.ToLower(payload.Status) {
	case "down":
		templateMessage = template.DownMessage
	case "up":
		templateMessage = template.UpMessage
	case "warning":
		templateMessage = template.WarningMessage
	case "paused":
		templateMessage = template.PausedMessage
	default:
		switch resourceType {
		case "cpu":
			if strings.Contains(strings.ToLower(payload.Status), "restore") {
				templateMessage = template.RestoreCPUMessage
			} else {
				templateMessage = template.CPUMessage
			}
		case "ram", "memory":
			if strings.Contains(strings.ToLower(payload.Status), "restore") {
				templateMessage = template.RestoreRAMMessage
			} else {
				templateMessage = template.RAMMessage
			}
		case "disk":
			if strings.Contains(strings.ToLower(payload.Status), "restore") {
				templateMessage = template.RestoreDiskMessage
			} else {
				templateMessage = template.DiskMessage
			}
		case "network":
			if strings.Contains(strings.ToLower(payload.Status), "restore") {
				templateMessage = template.RestoreNetworkMessage
			} else {
				templateMessage = template.NetworkMessage
			}
		case "cpu_temp", "cpu_temperature":
			if strings.Contains(strings.ToLower(payload.Status), "restore") {
				templateMessage = template.RestoreCPUTempMessage
			} else {
				templateMessage = template.CPUTempMessage
			}
		case "disk_io":
			if strings.Contains(strings.ToLower(payload.Status), "restore") {
				templateMessage = template.RestoreDiskIOMessage
			} else {
				templateMessage = template.DiskIOMessage
			}
		default:
			templateMessage = template.WarningMessage
		}
	}

	if templateMessage == "" {
		templateMessage = ms.generateDefaultServerMessage(payload, resourceType)
	}

	return ms.replacePlaceholders(templateMessage, payload)
}

func (ms *MatrixService) generateServiceMessage(payload *NotificationPayload, template *ServiceNotificationTemplate) string {
	var templateMessage string

	switch strings.ToLower(payload.Status) {
	case "up":
		templateMessage = template.UpMessage
	case "down":
		templateMessage = template.DownMessage
	case "maintenance":
		templateMessage = template.MaintenanceMessage
	case "incident":
		templateMessage = template.IncidentMessage
	case "resolved":
		templateMessage = template.ResolvedMessage
	case "warning":
		templateMessage = template.WarningMessage
	default:
		templateMessage = template.WarningMessage
	}

	if templateMessage == "" {
		templateMessage = ms.generateDefaultUptimeMessage(payload)
	}

	return ms.replacePlaceholders(templateMessage, payload)
}

func (ms *MatrixService) replacePlaceholders(message string, payload *NotificationPayload) string {
	message = strings.ReplaceAll(message, "${service_name}", payload.ServiceName)
	message = strings.ReplaceAll(message, "${status}", strings.ToUpper(payload.Status))
	message = strings.ReplaceAll(message, "${host}", ms.safeString(payload.Host))
	message = strings.ReplaceAll(message, "${hostname}", ms.safeString(payload.Hostname))

	u := ms.safeString(payload.URL)
	if u == "N/A" && payload.Host != "" {
		u = payload.Host
	}
	message = strings.ReplaceAll(message, "${url}", u)
	message = strings.ReplaceAll(message, "${domain}", ms.safeString(payload.Domain))

	if payload.ServiceType != "" {
		message = strings.ReplaceAll(message, "${service_type}", strings.ToUpper(payload.ServiceType))
	} else {
		message = strings.ReplaceAll(message, "${service_type}", "N/A")
	}

	message = strings.ReplaceAll(message, "${region_name}", ms.safeString(payload.RegionName))
	message = strings.ReplaceAll(message, "${agent_id}", ms.safeString(payload.AgentID))

	if payload.Port > 0 {
		message = strings.ReplaceAll(message, "${port}", fmt.Sprintf("%d", payload.Port))
	} else {
		message = strings.ReplaceAll(message, "${port}", "N/A")
	}

	if payload.ResponseTime > 0 {
		message = strings.ReplaceAll(message, "${response_time}", fmt.Sprintf("%dms", payload.ResponseTime))
	} else {
		message = strings.ReplaceAll(message, "${response_time}", "N/A")
	}

	if payload.Uptime > 0 {
		message = strings.ReplaceAll(message, "${uptime}", fmt.Sprintf("%d%%", payload.Uptime))
	} else {
		message = strings.ReplaceAll(message, "${uptime}", "N/A")
	}

	message = strings.ReplaceAll(message, "${cpu_usage}", ms.safeString(payload.CPUUsage))
	message = strings.ReplaceAll(message, "${ram_usage}", ms.safeString(payload.RAMUsage))
	message = strings.ReplaceAll(message, "${disk_usage}", ms.safeString(payload.DiskUsage))
	message = strings.ReplaceAll(message, "${network_usage}", ms.safeString(payload.NetworkUsage))
	message = strings.ReplaceAll(message, "${cpu_temp}", ms.safeString(payload.CPUTemp))
	message = strings.ReplaceAll(message, "${disk_io}", ms.safeString(payload.DiskIO))
	message = strings.ReplaceAll(message, "${threshold}", ms.safeString(payload.Threshold))
	message = strings.ReplaceAll(message, "${error_message}", ms.safeString(payload.ErrorMessage))
	message = strings.ReplaceAll(message, "${error}", ms.safeString(payload.ErrorMessage))
	message = strings.ReplaceAll(message, "${time}", payload.Timestamp.Format("2006-01-02 15:04:05"))
	message = strings.ReplaceAll(message, "${timestamp}", payload.Timestamp.Format("2006-01-02 15:04:05"))

	return message
}

func (ms *MatrixService) safeString(value string) string {
	if value == "" {
		return "N/A"
	}
	return value
}

func (ms *MatrixService) generateDefaultUptimeMessage(payload *NotificationPayload) string {
	statusEmoji := "🔵"
	switch strings.ToLower(payload.Status) {
	case "up":
		statusEmoji = "🟢"
	case "down":
		statusEmoji = "🔴"
	case "warning":
		statusEmoji = "🟡"
	case "maintenance", "paused":
		statusEmoji = "🟠"
	}

	message := fmt.Sprintf("%s Service %s is %s.", statusEmoji, payload.ServiceName, strings.ToUpper(payload.Status))

	details := []string{}
	if payload.URL != "" {
		details = append(details, fmt.Sprintf(" - Host URL: %s", payload.URL))
	} else if payload.Host != "" {
		details = append(details, fmt.Sprintf(" - Host: %s", payload.Host))
	}
	if payload.ServiceType != "" {
		details = append(details, fmt.Sprintf(" - Type: %s", strings.ToUpper(payload.ServiceType)))
	}
	if payload.Port > 0 {
		details = append(details, fmt.Sprintf(" - Port: %d", payload.Port))
	}
	if payload.Domain != "" {
		details = append(details, fmt.Sprintf(" - Domain: %s", payload.Domain))
	}
	if payload.ResponseTime > 0 {
		details = append(details, fmt.Sprintf(" - Response time: %dms", payload.ResponseTime))
	} else {
		details = append(details, " - Response time: N/A")
	}
	if payload.RegionName != "" {
		details = append(details, fmt.Sprintf(" - Region: %s", payload.RegionName))
	}
	if payload.Uptime > 0 {
		details = append(details, fmt.Sprintf(" - Uptime: %d%%", payload.Uptime))
	}
	details = append(details, fmt.Sprintf(" - Time: %s", payload.Timestamp.Format("2006-01-02 15:04:05")))

	if len(details) > 0 {
		message += "\n" + strings.Join(details, "\n")
	}
	return message
}

func (ms *MatrixService) generateDefaultServerMessage(payload *NotificationPayload, resourceType string) string {
	statusEmoji := "🔵"
	switch strings.ToLower(payload.Status) {
	case "up":
		statusEmoji = "🟢"
	case "down":
		statusEmoji = "🔴"
	case "warning":
		statusEmoji = "🟡"
	}
	return fmt.Sprintf("%s 🖥️ Server %s (%s) status: %s", statusEmoji, payload.ServiceName, payload.Hostname, strings.ToUpper(payload.Status))
}
