package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path"
	"strings"
	"time"
)

// SlackWebhookPayload represents the incoming Slack webhook format
type SlackWebhookPayload struct {
	Text        string `json:"text"`
	Username    string `json:"username,omitempty"`
	Channel     string `json:"channel,omitempty"`
	IconEmoji   string `json:"icon_emoji,omitempty"`
	Attachments []struct {
		Color     string `json:"color,omitempty"`
		Title     string `json:"title,omitempty"`
		Text      string `json:"text,omitempty"`
		Footer    string `json:"footer,omitempty"`
		Timestamp int64  `json:"ts,omitempty"`
	} `json:"attachments,omitempty"`
}

// Config holds the middleware configuration
type Config struct {
	NtfyBaseURL  string
	NtfyToken    string
	NtfyUsername string
	NtfyPassword string
	BindAddress  string
	BindPort     string
}

// Middleware handles the webhook forwarding
type Middleware struct {
	config Config
	client *http.Client
}

func NewMiddleware(config Config) *Middleware {
	return &Middleware{
		config: config,
		client: &http.Client{
			Timeout: 10 * time.Second,
		},
	}
}

// extractMessage extracts the alert message from various Slack webhook formats
func (m *Middleware) extractMessage(payload *SlackWebhookPayload) string {
	// Primary message text
	if payload.Text != "" {
		return payload.Text
	}

	// Check attachments for additional content
	if len(payload.Attachments) > 0 {
		var parts []string

		for _, attachment := range payload.Attachments {
			if attachment.Title != "" {
				parts = append(parts, attachment.Title)
			}
			if attachment.Text != "" {
				parts = append(parts, attachment.Text)
			}
		}

		if len(parts) > 0 {
			return strings.Join(parts, "\n")
		}
	}

	return "Generic Message"
}

// forwardToNtfy sends the message to the ntfy server
func (m *Middleware) forwardToNtfy(topic, message string) error {
	ntfyURL := fmt.Sprintf("%s/%s", strings.TrimRight(m.config.NtfyBaseURL, "/"), topic)

	req, err := http.NewRequest("POST", ntfyURL, bytes.NewBufferString(message))
	if err != nil {
		return fmt.Errorf("failed to create request: %w", err)
	}

	req.Header.Set("Content-Type", "text/plain; charset=utf-8")
	req.Header.Set("User-Agent", "slack-to-ntfy/1.0")

	if m.config.NtfyToken != "" {
		req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", m.config.NtfyToken))
	} else if m.config.NtfyUsername != "" && m.config.NtfyPassword != "" {
		req.SetBasicAuth(m.config.NtfyUsername, m.config.NtfyPassword)
	}

	req.Header.Set("Title", "Slack to ntfy Alert")
	req.Header.Set("Priority", "high")
	req.Header.Set("Tags", "warning,computer")

	resp, err := m.client.Do(req)
	if err != nil {
		return fmt.Errorf("failed to send request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("ntfy request failed with status %d: %s", resp.StatusCode, string(body))
	}

	log.Printf("Successfully forwarded message to ntfy topic '%s'", topic)
	return nil
}

// webhookHandler handles incoming Slack webhook requests
func (m *Middleware) webhookHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	// Extract topic from URL path
	topic := strings.TrimPrefix(r.URL.Path, "/")
	if topic == "" {
		http.Error(w, "Topic is required in URL path", http.StatusBadRequest)
		return
	}

	// Clean topic (remove any path traversal attempts)
	topic = path.Clean(topic)
	if strings.Contains(topic, "..") {
		http.Error(w, "Invalid topic name", http.StatusBadRequest)
		return
	}

	// Read request body
	body, err := io.ReadAll(r.Body)
	if err != nil {
		log.Printf("Failed to read request body: %v", err)
		http.Error(w, "Failed to read request", http.StatusBadRequest)
		return
	}

	var message string

	// Try to parse as JSON (Slack webhook format)
	var payload SlackWebhookPayload
	if err := json.Unmarshal(body, &payload); err == nil {
		message = m.extractMessage(&payload)
		log.Printf("Received Slack webhook for topic '%s': %s", topic, message)
	} else {
		// Fallback to raw text
		message = string(body)
		if message == "" {
			message = "Slack to ntfy Alert (no content)"
		}
		log.Printf("Received raw webhook for topic '%s': %s", topic, message)
	}

	// Forward to ntfy
	if err := m.forwardToNtfy(topic, message); err != nil {
		log.Printf("Failed to forward to ntfy: %v", err)
		http.Error(w, "Failed to forward alert", http.StatusInternalServerError)
		return
	}

	// Return success response
	w.Header().Set("Content-Type", "application/json")
	response := map[string]string{
		"status":  "success",
		"message": "Alert forwarded to ntfy",
		"topic":   topic,
	}
	json.NewEncoder(w).Encode(response)
}

// healthHandler provides a health check endpoint
func (m *Middleware) healthHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	response := map[string]interface{}{
		"status":       "healthy",
		"ntfy_url":     m.config.NtfyBaseURL,
		"auth_enabled": m.config.NtfyToken != "" || m.config.NtfyUsername != "",
		"timestamp":    time.Now().Unix(),
	}
	json.NewEncoder(w).Encode(response)
}

func loadConfig() Config {
	return Config{
		NtfyBaseURL:  getEnv("NTFY_BASE_URL", "https://ntfy.sh"),
		NtfyToken:    getEnv("NTFY_TOKEN", ""),
		NtfyUsername: getEnv("NTFY_USERNAME", ""),
		NtfyPassword: getEnv("NTFY_PASSWORD", ""),
		BindAddress:  getEnv("BIND_ADDRESS", "0.0.0.0"),
		BindPort:     getEnv("BIND_PORT", "8080"),
	}
}

func getEnv(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}

func main() {
	config := loadConfig()
	middleware := NewMiddleware(config)

	// Setup routes
	http.HandleFunc("/health", middleware.healthHandler)
	http.HandleFunc("/", middleware.webhookHandler)

	// Start server
	bindAddr := fmt.Sprintf("%s:%s", config.BindAddress, config.BindPort)

	log.Printf("Starting Slack to ntfy middleware server")
	log.Printf("Listening on: %s", bindAddr)
	log.Printf("ntfy URL: %s", config.NtfyBaseURL)
	log.Printf("Authentication: %t", config.NtfyToken != "" || config.NtfyUsername != "")

	if err := http.ListenAndServe(bindAddr, nil); err != nil {
		log.Fatalf("Server failed to start: %v", err)
	}
}
