package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"sync"
	"time"
	"strings"
)

// Camera represents a single camera
type Camera struct {
	Host string `json:"host"`
	Port string `json:"port"`
}

// Config represents the full JSON config
type Config struct {
	ImagesPath          string            `json:"images_path"`
	NtfyURL             string            `json:"ntfy_url"`
	PollIntervalMinutes int               `json:"poll_interval_minutes"`
	Cameras             map[string]Camera `json:"cameras"`
}

var config Config

func main() {
	loadConfig()

	interval := time.Duration(config.PollIntervalMinutes) * time.Minute
	ticker := time.NewTicker(interval)
	defer ticker.Stop()

	// Run immediately at startup
	go pollImages()

	for range ticker.C {
		go pollImages()
	}

	select {} // keep program running
}

// loadConfig searches multiple locations for the JSON config
func loadConfig() {
	paths := []string{
		"/usr/local/etc/camcap.json",
		"/usr/etc/camcap.json",
		"./camcap.json",
	}

	var configFile string
	for _, path := range paths {
		if _, err := os.Stat(path); err == nil {
			configFile = path
			break
		}
	}

	if configFile == "" {
		panic("No configuration file found in /usr/local/etc, /usr/etc, or current directory")
	}

	f, err := os.Open(configFile)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	if err := json.NewDecoder(f).Decode(&config); err != nil {
		panic(err)
	}

	fmt.Println("Loaded config from:", configFile)
}

// pollImages downloads snapshots concurrently
func pollImages() {
	fmt.Println("Starting image poll at", time.Now())

	var wg sync.WaitGroup
	errCh := make(chan error, len(config.Cameras))

	for title, cam := range config.Cameras {
		url := fmt.Sprintf("http://%s.service:%s/snapshot", cam.Host, cam.Port)

		wg.Add(1)
		go func(title, url string) {
			defer wg.Done()
			if err := downloadImageWithTitle(url, title); err != nil {
				errCh <- fmt.Errorf("%s: %v", title, err)
			} else {
				fmt.Println("Downloaded:", title)
			}
		}(title, url)
	}

	wg.Wait()
	close(errCh)

	for err := range errCh {
		fmt.Println("Error:", err)
	}

	fmt.Println("Finished image poll at", time.Now())
}

// downloadImageWithTitle saves the image to timestamped directories
func downloadImageWithTitle(url, title string) error {
	today := time.Now()
	dir := filepath.Join(config.ImagesPath, title,
		fmt.Sprintf("%d", today.Year()),
		fmt.Sprintf("%02d", today.Month()),
		fmt.Sprintf("%02d", today.Day()))

	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
		return err
	}

	timestamp := today.Format("20060102150405")
	fileName := fmt.Sprintf("%s-%s.jpg", title, timestamp)
	filePath := filepath.Join(dir, fileName)

	resp, err := http.Get(url)
	if err != nil {
		notifyError(fmt.Sprintf("Connection error for %s: %v", title, err))
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		notifyError(fmt.Sprintf("HTTP error for %s: %s", title, resp.Status))
		return fmt.Errorf("bad status: %s", resp.Status)
	}

	out, err := os.Create(filePath)
	if err != nil {
		notifyError(fmt.Sprintf("File creation error for %s: %v", title, err))
		return err
	}
	defer out.Close()

	_, err = io.Copy(out, resp.Body)
	if err != nil {
		notifyError(fmt.Sprintf("File write error for %s: %v", title, err))
	}

	return err
}

// notifyError posts errors to ntfy
func notifyError(message string) {
	resp, err := http.Post(config.NtfyURL, "text/plain", strings.NewReader(message))
	if err != nil {
		fmt.Println("Failed to send ntfy notification:", err)
		return
	}
	defer resp.Body.Close()
	fmt.Println("Sent ntfy error:", message)
}

