package main

import (
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"text/template"
	"time"
)

type WebhookPayload struct {
	Event   string      `json:"event"`
	Timestamp time.Time `json:"timestamp"`
	Server  *Server    `json:"server,omitempty"`
	Peer    *Peer      `json:"peer,omitempty"`
	Policy  *Policy    `json:"policy,omitempty"`
}

type Policy struct {
	Action string   `json:"action"`
	Changes []string `json:"changes"`
}

type WebhookQueueItem struct {
	Webhook *Webhook
	Payload *WebhookPayload
	Retries int
	NextBackoff time.Duration
}

var webhookQueue chan WebhookQueueItem

func initWebhookEngine() {
	webhookQueue = make(chan WebhookQueueItem, 100)
	go webhookWorker()
}

func webhookWorker() {
	for item := range webhookQueue {
		if !item.Webhook.IsEnabled {
			continue
		}
		err := sendWebhook(item.Webhook, item.Payload)
		if err != nil && item.Retries < 3 {
			item.Retries++
			if item.NextBackoff == 0 {
				item.NextBackoff = 2 * time.Second
			} else {
				item.NextBackoff *= 2
			}
			time.Sleep(item.NextBackoff)
			webhookQueue <- item
		}
	}
}

func RegisterWebhook(serverID uint, name, url, template, customBody string, headers map[string]string, actions []string) (uint, error) {
	headersJSON, _ := json.Marshal(headers)
	actionsJSON, _ := json.Marshal(actions)
	webhook := Webhook{
		ServerID:         serverID,
		Name:             name,
		URL:              url,
		Template:         template,
		CustomBody:       customBody,
		CustomHeaders:    JSON(headersJSON),
		SubscribedActions: JSON(actionsJSON),
		IsEnabled:        true,
		VerifySSL:        true,
	}
	if err := db.Create(&webhook).Error; err != nil {
		return 0, err
	}
	return webhook.ID, nil
}

func UpdateWebhook(id uint, name, url, template, customBody string, headers map[string]string, actions []string, isEnabled, verifySSL bool) error {
	headersJSON, _ := json.Marshal(headers)
	actionsJSON, _ := json.Marshal(actions)
	return db.Model(&Webhook{}).Where("id = ?", id).Updates(map[string]interface{}{
		"name":              name,
		"url":               url,
		"template":          template,
		"custom_body":       customBody,
		"custom_headers":    JSON(headersJSON),
		"subscribed_actions": JSON(actionsJSON),
		"is_enabled":        isEnabled,
		"verify_ssl":        verifySSL,
	}).Error
}

func ToggleWebhook(id uint, enabled bool) error {
	return db.Model(&Webhook{}).Where("id = ?", id).Update("is_enabled", enabled).Error
}

func TriggerWebhook(event string, server *Server, peer *Peer, action string, changes []string) error {
	var webhooks []Webhook
	db.Where("server_id = ? OR is_global = ?", server.ID, true).Find(&webhooks)
	
	for _, wh := range webhooks {
		if !wh.IsEnabled {
			continue
		}
		var actions []string
		json.Unmarshal(wh.SubscribedActions, &actions)
		if !contains(actions, event) {
			continue
		}
		
		payload := &WebhookPayload{
			Event:   event,
			Timestamp: time.Now().UTC(),
			Server:  server,
			Peer:    peer,
			Policy:  &Policy{Action: action, Changes: changes},
		}
		webhookQueue <- WebhookQueueItem{Webhook: &wh, Payload: payload}
	}
	return nil
}

func sendWebhook(wh *Webhook, payload *WebhookPayload) error {
	body, err := buildWebhookBody(wh, payload)
	if err != nil {
		return err
	}
	
	client := &http.Client{}
	if !wh.VerifySSL {
		client.Transport = &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		}
	}
	
	req, err := http.NewRequest("POST", wh.URL, bytes.NewBuffer(body))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	
	var headers map[string]string
	if len(wh.CustomHeaders) > 0 {
		json.Unmarshal(wh.CustomHeaders, &headers)
		for k, v := range headers {
			req.Header.Set(k, v)
		}
	}
	
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		bodyBytes, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("webhook failed: %d %s", resp.StatusCode, string(bodyBytes))
	}
	return nil
}

func buildWebhookBody(wh *Webhook, payload *WebhookPayload) ([]byte, error) {
	switch wh.Template {
	case "mikrotik":
		return buildMikrotikBody(payload)
	case "custom":
		if wh.CustomBody == "" {
			return json.Marshal(payload)
		}
		tmpl, err := template.New("custom").Parse(wh.CustomBody)
		if err != nil {
			return nil, err
		}
		var buf bytes.Buffer
		if err := tmpl.Execute(&buf, payload); err != nil {
			return nil, err
		}
		return buf.Bytes(), nil
	default:
		return json.Marshal(payload)
	}
}

func buildMikrotikBody(payload *WebhookPayload) ([]byte, error) {
	mikrotikTemplate := `{
		"action": "{{.Policy.Action}}",
		"peer": {
			"public_key": "{{.Peer.PublicKey}}",
			"ip": "{{.Peer.IP}}",
			"allow_access": "{{.Peer.AllowAccess}}",
			"allow_internet": {{.Peer.AllowInternet}}
		},
		"server": {
			"name": "{{.Server.Name}}",
			"mode": "{{.Server.Mode}}"
		}
	}`
	tmpl, err := template.New("mikrotik").Parse(mikrotikTemplate)
	if err != nil {
		return nil, err
	}
	var buf bytes.Buffer
	if err := tmpl.Execute(&buf, payload); err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}

func TestWebhook(id uint) error {
	var wh Webhook
	if err := db.First(&wh, id).Error; err != nil {
		return err
	}
	payload := &WebhookPayload{
		Event:   "test",
		Timestamp: time.Now().UTC(),
		Server:  &Server{Name: "Test Server", Mode: "standalone"},
		Peer:    &Peer{PublicKey: "test_pub_key", IP: "10.0.0.2", AllowAccess: JSON([]byte(`["192.168.1.0/24"]`)), AllowInternet: true},
		Policy:  &Policy{Action: "test", Changes: []string{"test_change"}},
	}
	return sendWebhook(&wh, payload)
}

func contains(slice []string, item string) bool {
	for _, s := range slice {
		if s == item {
			return true
		}
	}
	return false
}
