package services

import (
	"bytes"
	"context"
	"crypto/rand"
	"crypto/sha256"
	"crypto/subtle"
	"crypto/tls"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/smtp"
	"strings"
	"time"

	"github.com/mrhid6/vantage/server/internal/db"
	"github.com/mrhid6/vantage/server/internal/models"
	"go.mongodb.org/mongo-driver/v2/bson"
	"go.mongodb.org/mongo-driver/v2/mongo"
	"go.mongodb.org/mongo-driver/v2/mongo/options"
)

var defaultSettings = models.Settings{
	Alerts: models.AlertSettings{
		Enabled:                 false,
		WebhookURL:              "",
		OfflineThresholdMinutes: 5,
	},
	Email: models.EmailSettings{
		SMTPPort: 587,
	},
}

func EnsureSettingsIndexes() error {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := db.Col("settings").Indexes().DropOne(ctx, "secrets.read_token_hash_1"); err != nil && !isIndexNotFound(err) {
		return err
	}

	if _, err := db.Col("settings").Indexes().CreateOne(ctx, mongo.IndexModel{
		Keys:    bson.D{{Key: "instance_id", Value: 1}},
		Options: options.Index().SetUnique(true),
	}); err != nil {
		return err
	}

	_, err := db.Col("settings").Indexes().CreateOne(ctx, mongo.IndexModel{
		Keys: bson.D{{Key: "secrets.read_token_hash", Value: 1}},
		Options: options.Index().SetUnique(true).SetName("settings_read_token_hash_unique").
			SetPartialFilterExpression(bson.M{
				"secrets.read_token_hash": bson.M{"$type": "string", "$gt": ""},
			}),
	})
	return err
}

func GetSettings(instanceID string) (*models.Settings, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	var s models.Settings
	err := db.Col("settings").FindOne(ctx, bson.M{"instance_id": instanceID}).Decode(&s)
	if err == mongo.ErrNoDocuments {
		cp := defaultSettings
		cp.InstanceID = instanceID
		return &cp, nil
	}
	if err != nil {
		return nil, err
	}
	s.Secrets.ReadTokenSet = s.Secrets.ReadTokenHash != ""
	return &s, nil
}

func hashToken(token string) string {
	sum := sha256.Sum256([]byte(token))
	return hex.EncodeToString(sum[:])
}

func RotateSecretsReadToken(instanceID string) (string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	raw := make([]byte, 32)
	if _, err := rand.Read(raw); err != nil {
		return "", err
	}
	token := hex.EncodeToString(raw)

	_, err := db.Col("settings").UpdateOne(ctx,
		bson.M{"instance_id": instanceID},
		bson.M{
			"$set": bson.M{
				"secrets.read_token_hash": hashToken(token),
				"secrets.rotated_at":      time.Now(),
			},
			"$setOnInsert": bson.M{"instance_id": instanceID},
		},
		options.UpdateOne().SetUpsert(true),
	)
	if err != nil {
		return "", err
	}
	return token, nil
}

func ResolveSecretsReadToken(token string) (string, bool) {
	if token == "" {
		return "", false
	}
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	var s models.Settings
	err := db.Col("settings").FindOne(ctx, bson.M{"secrets.read_token_hash": hashToken(token)}).Decode(&s)
	if err != nil || s.Secrets.ReadTokenHash == "" || s.InstanceID == "" {
		return "", false
	}
	expected, err := hex.DecodeString(s.Secrets.ReadTokenHash)
	if err != nil {
		return "", false
	}
	got := sha256.Sum256([]byte(token))
	if subtle.ConstantTimeCompare(expected, got[:]) != 1 {
		return "", false
	}
	return s.InstanceID, true
}

func SaveSettings(instanceID string, alerts models.AlertSettings, email models.EmailSettings, retentionDays *int) error {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if alerts.OfflineThresholdMinutes <= 0 {
		alerts.OfflineThresholdMinutes = 5
	}
	if email.SMTPPort <= 0 {
		email.SMTPPort = 587
	}

	set := bson.M{"alerts": alerts, "email": email}
	if retentionDays != nil {
		set["workflow_log_retention_days"] = *retentionDays
	}
	_, err := db.Col("settings").UpdateOne(ctx,
		bson.M{"instance_id": instanceID},
		bson.M{"$set": set, "$setOnInsert": bson.M{"instance_id": instanceID}},
		options.UpdateOne().SetUpsert(true),
	)
	return err
}

func GetWorkflowLogRetentionDays(instanceID string) (int, error) {
	s, err := GetSettings(instanceID)
	if err != nil {
		return 30, err
	}
	if s.WorkflowLogRetentionDays == nil {
		return 30, nil
	}
	return *s.WorkflowLogRetentionDays, nil
}

func SendOfflineWebhook(webhookURL, hostname, serverID, ipAddress string) {
	payload := map[string]any{
		"event":      "server.offline",
		"hostname":   hostname,
		"server_id":  serverID,
		"ip_address": ipAddress,
		"timestamp":  time.Now().UTC().Format(time.RFC3339),
		"message":    fmt.Sprintf("Server %s (%s) has gone offline", hostname, ipAddress),
	}
	body, err := json.Marshal(payload)
	if err != nil {
		log.Printf("webhook marshal error: %v", err)
		return
	}
	resp, err := http.Post(webhookURL, "application/json", bytes.NewReader(body))
	if err != nil {
		log.Printf("webhook delivery error for %s: %v", hostname, err)
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode >= 400 {
		log.Printf("webhook returned %d for %s", resp.StatusCode, hostname)
	}
}

func SendOfflineEmail(cfg models.EmailSettings, hostname, serverID, ipAddress string) {
	if !cfg.Enabled || cfg.SMTPHost == "" || len(cfg.ToAddrs) == 0 {
		return
	}

	subject := fmt.Sprintf("Vantage Alert: %s is offline", hostname)
	bodyText := fmt.Sprintf(
		"Server %s (%s) has gone offline.\r\n\r\nServer ID: %s\r\nTimestamp: %s\r\n",
		hostname, ipAddress, serverID, time.Now().UTC().Format(time.RFC3339),
	)

	msg := []byte(fmt.Sprintf(
		"From: %s\r\nTo: %s\r\nSubject: %s\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n%s",
		cfg.FromAddr,
		strings.Join(cfg.ToAddrs, ", "),
		subject,
		bodyText,
	))

	addr := fmt.Sprintf("%s:%d", cfg.SMTPHost, cfg.SMTPPort)
	var auth smtp.Auth
	if cfg.Username != "" {
		auth = smtp.PlainAuth("", cfg.Username, cfg.Password, cfg.SMTPHost)
	}

	var sendErr error
	if cfg.UseTLS {
		sendErr = sendMailTLS(addr, cfg.SMTPHost, auth, cfg.FromAddr, cfg.ToAddrs, msg)
	} else {
		sendErr = smtp.SendMail(addr, auth, cfg.FromAddr, cfg.ToAddrs, msg)
	}
	if sendErr != nil {
		log.Printf("email alert error for %s: %v", hostname, sendErr)
	}
}

func sendMailTLS(addr, host string, auth smtp.Auth, from string, to []string, msg []byte) error {
	conn, err := tls.Dial("tcp", addr, &tls.Config{ServerName: host})
	if err != nil {
		return fmt.Errorf("tls dial: %w", err)
	}
	c, err := smtp.NewClient(conn, host)
	if err != nil {
		return fmt.Errorf("smtp client: %w", err)
	}
	defer c.Close()

	if auth != nil {
		if err := c.Auth(auth); err != nil {
			return fmt.Errorf("smtp auth: %w", err)
		}
	}
	if err := c.Mail(from); err != nil {
		return err
	}
	for _, rcpt := range to {
		if err := c.Rcpt(strings.TrimSpace(rcpt)); err != nil {
			return err
		}
	}
	w, err := c.Data()
	if err != nil {
		return err
	}
	if _, err := w.Write(msg); err != nil {
		return err
	}
	if err := w.Close(); err != nil {
		return err
	}
	return c.Quit()
}
