package main

import (
	"fmt"
	"io"
	"strings"

	"github.com/einand/applephotos/internal/photos"
)

func run(args []string, stdout, stderr io.Writer, bridge photos.Bridge) int {
	if len(args) < 1 {
		usage(stderr)
		return 1
	}

	cmd := args[0]
	switch cmd {
	case "albums":
		return cmdAlbums(stdout, stderr, bridge)
	case "photos":
		return cmdPhotos(args[1:], stdout, stderr, bridge)
	case "tree":
		return cmdTree(stdout, stderr, bridge)
	case "backup-all":
		return cmdBackupAll(args[1:], stdout, stderr, bridge)
	case "export":
		return cmdExport(args[1:], stdout, stderr, bridge)
	case "help", "--help", "-h":
		usage(stderr)
		return 0
	default:
		fmt.Fprintf(stderr, "unknown command: %s\n", cmd)
		usage(stderr)
		return 1
	}
}

func usage(w io.Writer) {
	fmt.Fprintln(w, `applephotos — export optimized images from Apple Photos

Usage:
  applephotos albums
  applephotos photos --album-id <id>
  applephotos tree
  applephotos backup-all --out <dir> [--size <px>] [--originals]
  applephotos export --album-id <id> --out <dir> [--size <px>] [--originals]

Commands:
  albums    List user-created albums
  photos    List photo assets in an album
  tree      Show folder and album hierarchy
  backup-all Export all albums into the Photos folder tree
  export    Export optimized JPEG previews or original files

Flags:
  --album-id <id>   Album local identifier or title (required for photos/export)
  --out <dir>       Output directory (required for export/backup-all)
  --size <px>       Target longest-side in pixels (default: 1024, preview export only)
  --originals       Export original files instead of JPEG previews`)
}

func mustAuth(stderr io.Writer, bridge photos.Bridge) int {
	if err := bridge.RequestAccess(); err != nil {
		fmt.Fprintf(stderr, "error: %v\n", err)
		return 1
	}
	return 0
}

func cmdAlbums(stdout, stderr io.Writer, bridge photos.Bridge) int {
	if rc := mustAuth(stderr, bridge); rc != 0 {
		return rc
	}
	albums, err := bridge.ListAlbums()
	if err != nil {
		fmt.Fprintf(stderr, "error: %v\n", err)
		return 1
	}
	for _, a := range albums {
		fmt.Fprintf(stdout, "%s\t%s\n", a.ID, a.Title)
	}
	return 0
}

func resolveAlbumID(bridge photos.Bridge, idOrName string) (string, error) {
	_, _, err := bridge.ListAssets(idOrName)
	if err == nil {
		return idOrName, nil
	}
	albums, listErr := bridge.ListAlbums()
	if listErr != nil {
		return idOrName, err
	}
	for _, a := range albums {
		if a.Title == idOrName {
			return a.ID, nil
		}
	}
	return idOrName, err
}

func cmdPhotos(args []string, stdout, stderr io.Writer, bridge photos.Bridge) int {
	albumID := flagVal(args, "--album-id")
	if albumID == "" {
		fmt.Fprintln(stderr, "error: --album-id is required")
		return 1
	}
	if rc := mustAuth(stderr, bridge); rc != 0 {
		return rc
	}
	resolved, err := resolveAlbumID(bridge, albumID)
	if err != nil {
		fmt.Fprintf(stderr, "error: %v\n", err)
		return 1
	}
	assets, _, err := bridge.ListAssets(resolved)
	if err != nil {
		fmt.Fprintf(stderr, "error: %v\n", err)
		return 1
	}
	for _, a := range assets {
		cloud := "local"
		if a.Cloud {
			cloud = "cloud"
		}
		fmt.Fprintf(stdout, "%s\t%s\t%s\n", a.ID, a.Filename, cloud)
	}
	return 0
}

func cmdTree(stdout, stderr io.Writer, bridge photos.Bridge) int {
	if rc := mustAuth(stderr, bridge); rc != 0 {
		return rc
	}
	nodes, err := bridge.ListTree()
	if err != nil {
		fmt.Fprintf(stderr, "error: %v\n", err)
		return 1
	}
	for _, node := range nodes {
		printNode(stdout, node, 0)
	}
	return 0
}

func cmdExport(args []string, stdout, stderr io.Writer, bridge photos.Bridge) int {
	albumID := flagVal(args, "--album-id")
	outDir := flagVal(args, "--out")
	originals := hasFlag(args, "--originals")
	sizeStr := flagValWithDefault(args, "--size", "1024")

	if albumID == "" {
		fmt.Fprintln(stderr, "error: --album-id is required")
		return 1
	}
	if outDir == "" {
		fmt.Fprintln(stderr, "error: --out is required")
		return 1
	}

	if rc := mustAuth(stderr, bridge); rc != 0 {
		return rc
	}

	resolved, err := resolveAlbumID(bridge, albumID)
	if err != nil {
		fmt.Fprintf(stderr, "error: %v\n", err)
		return 1
	}

	var size int
	if !originals {
		if _, err2 := fmt.Sscanf(sizeStr, "%d", &size); err2 != nil || size <= 0 {
			fmt.Fprintf(stderr, "error: --size must be a positive integer, got %q\n", sizeStr)
			return 1
		}
	}

	assets, total, err := bridge.ListAssets(resolved)
	if err != nil {
		fmt.Fprintf(stderr, "error: %v\n", err)
		return 1
	}

	exported := 0
	failed := 0
	for i, a := range assets {
		var result photos.ExportResult
		var exportErr error
		if originals {
			result, exportErr = bridge.ExportOriginal(a.ID, outDir, i)
		} else {
			result, exportErr = bridge.ExportPreview(a.ID, outDir, size, i)
		}

		progressBar(stderr, exported+failed+1, total, result.Filename, result.Size, result.Cloud)

		if exportErr != nil {
			failed++
			continue
		}
		exported++
	}

	if exported == 0 && failed > 0 {
		fmt.Fprintf(stderr, "\nerror: all exports failed\n")
		return 1
	}

	if originals {
		fmt.Fprintf(stderr, "\nexported %d original files to %s\n", exported, outDir)
	} else {
		fmt.Fprintf(stderr, "\nexported %d photos to %s\n", exported, outDir)
	}
	return 0
}

func cmdBackupAll(args []string, stdout, stderr io.Writer, bridge photos.Bridge) int {
	outDir := flagVal(args, "--out")
	originals := hasFlag(args, "--originals")
	sizeStr := flagValWithDefault(args, "--size", "1024")

	if outDir == "" {
		fmt.Fprintln(stderr, "error: --out is required")
		return 1
	}

	if rc := mustAuth(stderr, bridge); rc != 0 {
		return rc
	}

	var size int
	if !originals {
		if _, err := fmt.Sscanf(sizeStr, "%d", &size); err != nil || size <= 0 {
			fmt.Fprintf(stderr, "error: --size must be a positive integer, got %q\n", sizeStr)
			return 1
		}
	}

	nodes, err := bridge.ListTree()
	if err != nil {
		fmt.Fprintf(stderr, "error: %v\n", err)
		return 1
	}
	albumCount := countAlbums(nodes)

	totalAssets, grandTotal, err := backupTree(nodes, outDir, size, originals, stderr, bridge)
	if err != nil {
		fmt.Fprintf(stderr, "error: %v\n", err)
		return 1
	}

	if originals {
		fmt.Fprintf(stderr, "\nexported %d original files across %d albums to %s\n", totalAssets, albumCount, outDir)
	} else {
		fmt.Fprintf(stderr, "\nexported %d preview files across %d albums to %s\n", totalAssets, albumCount, outDir)
	}
	_ = grandTotal
	return 0
}

func backupTree(nodes []photos.CollectionNode, outDir string, targetSize int, originals bool, stderr io.Writer, bridge photos.Bridge) (int, int, error) {
	exported := 0
	total := 0
	for _, node := range nodes {
		path := outDir + "/" + sanitizePathComponent(node.Name)
		if node.Kind == "folder" {
			n, t, err := backupTree(node.Children, path, targetSize, originals, stderr, bridge)
			if err != nil {
				return exported, total, err
			}
			exported += n
			total += t
			continue
		}
		if node.Kind == "album" && node.ID != "" {
			assets, assetTotal, err := bridge.ListAssets(node.ID)
			if err != nil {
				continue
			}
			total += assetTotal
			for i, a := range assets {
				var result photos.ExportResult
				var exportErr error
				if originals {
					result, exportErr = bridge.ExportOriginal(a.ID, path, i)
				} else {
					result, exportErr = bridge.ExportPreview(a.ID, path, targetSize, i)
				}
				progressBar(stderr, exported+1, total, path+"/"+result.Filename, result.Size, result.Cloud)
				if exportErr != nil {
					continue
				}
				exported++
			}
		}
	}
	return exported, total, nil
}

func sanitizePathComponent(name string) string {
	s := strings.TrimSpace(name)
	if s == "" {
		s = "Untitled"
	}
	s = strings.ReplaceAll(s, "/", "_")
	s = strings.ReplaceAll(s, "\\", "_")
	return s
}

func flagVal(args []string, name string) string {
	return flagValWithDefault(args, name, "")
}

func hasFlag(args []string, name string) bool {
	for _, arg := range args {
		if arg == name {
			return true
		}
	}
	return false
}

func printNode(w io.Writer, node photos.CollectionNode, depth int) {
	for i := 0; i < depth; i++ {
		fmt.Fprint(w, "  ")
	}
	fmt.Fprintln(w, node.Name)
	for _, child := range node.Children {
		printNode(w, child, depth+1)
	}
}

func countAlbums(nodes []photos.CollectionNode) int {
	total := 0
	for _, node := range nodes {
		if node.Kind == "album" {
			total++
		}
		total += countAlbums(node.Children)
	}
	return total
}

func flagValWithDefault(args []string, name, def string) string {
	for i, arg := range args {
		if arg == name && i+1 < len(args) {
			return args[i+1]
		}
	}
	return def
}

func progressBar(w io.Writer, current, total int, filename string, size int64, cloud string) {
	pct := 0
	if total > 0 {
		pct = current * 100 / total
	}
	barWidth := 30
	filled := pct * barWidth / 100
	bar := strings.Repeat("=", filled) + strings.Repeat(" ", barWidth-filled)
	fmt.Fprintf(w, "\r[%s] %3d%%  %s  %s  %s", bar, pct, filename, formatSize(size), cloud)
}

func formatSize(bytes int64) string {
	if bytes <= 0 {
		return ""
	}
	const kb = 1024
	const mb = kb * 1024
	if bytes >= mb {
		return fmt.Sprintf("%.1f MB", float64(bytes)/float64(mb))
	}
	return fmt.Sprintf("%.1f KB", float64(bytes)/float64(kb))
}
