package main

import (
    "encoding/json"
    "fmt"
    "html/template"
    "net"
    "net/http"
    "os"
    "path/filepath"
    "time"
)

type Service struct {
    Name string `json:"name"`
    Host string `json:"host"`
    Port int    `json:"port"`
}

type Result struct {
    Service Service `json:"service"`
    Up      bool    `json:"up"`
    Latency int64   `json:"latency_ms"`
    Error   string  `json:"error"`
}

type Summary struct {
    CheckedAt string   `json:"checked_at"`
    Results   []Result `json:"results"`
}

var (
    dataDir      = "data"
    servicesFile = filepath.Join(dataDir, "services.json")
    pageTmpl     = template.Must(template.New("page").Parse(`
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<title>Service Watch Dashboard Go</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body{font-family:Segoe UI,Arial,sans-serif;background:#f8fafc;color:#0f172a;margin:0}
header{background:linear-gradient(135deg,#0f172a,#1d4ed8);color:#fff;padding:20px 24px}
main{padding:24px}
.card{background:#fff;border-radius:16px;padding:16px;box-shadow:0 8px 24px rgba(15,23,42,.08)}
.toolbar{display:flex;gap:12px;align-items:center;justify-content:space-between;flex-wrap:wrap}
table{width:100%;border-collapse:collapse}
th,td{padding:10px;border-bottom:1px solid #e2e8f0;text-align:left}
.badge-ok{color:#065f46;font-weight:700}
.badge-fail{color:#991b1b;font-weight:700}
button{background:#2563eb;color:white;border:none;border-radius:10px;padding:10px 14px;cursor:pointer}
small{color:#475569}
.muted{color:#475569}
</style>
</head>
<body>
<header>
  <h1>Service Watch Dashboard Go</h1>
  <div>Dashboard monitoring ringan untuk service internal dan endpoint TCP.</div>
</header>
<main>
  <div class="card">
    <div class="toolbar">
      <div>
        <p style="margin:0 0 6px 0;"><button onclick="window.location='/?refresh=1'">Perbarui Dashboard</button></p>
        <small>Terakhir dicek: {{.CheckedAt}}</small>
      </div>
      <div class="muted">Klik tombol untuk memeriksa ulang service tanpa keluar dari tampilan dashboard.</div>
    </div>
    <table>
      <thead>
        <tr><th>Service</th><th>Host</th><th>Port</th><th>Status</th><th>Latency</th><th>Error</th></tr>
      </thead>
      <tbody>
      {{range .Results}}
        <tr>
          <td>{{.Service.Name}}</td>
          <td>{{.Service.Host}}</td>
          <td>{{.Service.Port}}</td>
          <td>{{if .Up}}<span class="badge-ok">UP{{else}}<span class="badge-fail">DOWN{{end}}</span></td>
          <td>{{.Latency}} ms</td>
          <td>{{.Error}}</td>
        </tr>
      {{end}}
      </tbody>
    </table>
  </div>
</main>
</body>
</html>`))
)

func ensureFiles() error {
    if err := os.MkdirAll(dataDir, 0o755); err != nil {
        return err
    }
    if _, err := os.Stat(servicesFile); err == nil {
        return nil
    }
    defaultServices := []Service{
        {Name: "Web Lokal", Host: "localhost", Port: 80},
        {Name: "SSH Lokal", Host: "localhost", Port: 22},
        {Name: "DNS Google", Host: "8.8.8.8", Port: 53},
    }
    b, _ := json.MarshalIndent(defaultServices, "", "  ")
    return os.WriteFile(servicesFile, b, 0o644)
}

func loadServices() ([]Service, error) {
    if err := ensureFiles(); err != nil {
        return nil, err
    }
    b, err := os.ReadFile(servicesFile)
    if err != nil {
        return nil, err
    }
    var services []Service
    if err := json.Unmarshal(b, &services); err != nil {
        return nil, err
    }
    return services, nil
}

func checkServices() Summary {
    services, err := loadServices()
    summary := Summary{CheckedAt: time.Now().Format("2006-01-02 15:04:05")}
    if err != nil {
        return summary
    }

    results := make([]Result, 0, len(services))
    for _, svc := range services {
        start := time.Now()
        address := fmt.Sprintf("%s:%d", svc.Host, svc.Port)
        conn, err := net.DialTimeout("tcp", address, 1200*time.Millisecond)
        latency := time.Since(start).Milliseconds()

        res := Result{Service: svc, Up: err == nil, Latency: latency}
        if err != nil {
            res.Error = err.Error()
        }
        if conn != nil {
            _ = conn.Close()
        }
        results = append(results, res)
    }

    summary.Results = results
    return summary
}

func main() {
    if os.Getenv("SERVICE_WATCH_SMOKE") == "1" {
        summary := checkServices()
        out, _ := json.MarshalIndent(summary, "", "  ")
        fmt.Println(string(out))
        return
    }

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        _ = r.ParseForm()
        _ = pageTmpl.Execute(w, checkServices())
    })

    http.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json; charset=utf-8")
        _ = json.NewEncoder(w).Encode(map[string]any{
            "app":    "service-watch-dashboard-go",
            "status": "ok",
            "port":   8080,
        })
    })

    http.HandleFunc("/api/check", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json; charset=utf-8")
        _ = json.NewEncoder(w).Encode(checkServices())
    })

    fmt.Println("Service Watch Dashboard berjalan di http://localhost:8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}
