package watcher

import (
	"crypto/sha256"
	"encoding/hex"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/goko/jingtian-tracker/config"
)

type FileEvent struct {
	Path    string
	Hash    string
	Size    int64
	ModTime time.Time
}

type Watcher struct {
	cfg       *config.Config
	watcher   *fsnotify.Watcher
	pending   map[string]time.Time // path -> first seen time
	pendingMu sync.Mutex
	Events    chan FileEvent
	done      chan struct{}
}

func New(cfg *config.Config) (*Watcher, error) {
	fsWatcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}

	w := &Watcher{
		cfg:     cfg,
		watcher: fsWatcher,
		pending: make(map[string]time.Time),
		Events:  make(chan FileEvent, 100),
		done:    make(chan struct{}),
	}

	return w, nil
}

func (w *Watcher) Start() error {
	// Add all directories recursively
	err := filepath.Walk(w.cfg.Watch.Root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return nil // Skip errors
		}
		if info.IsDir() {
			// Check exclusions
			for _, excl := range w.cfg.Watch.Exclude {
				if strings.Contains(path, excl) {
					return filepath.SkipDir
				}
			}
			return w.watcher.Add(path)
		}
		return nil
	})
	if err != nil {
		return err
	}

	// Start event loop
	go w.eventLoop()

	// Start stability checker
	go w.stabilityChecker()

	log.Printf("[watcher] Started watching: %s", w.cfg.Watch.Root)
	return nil
}

func (w *Watcher) Stop() {
	close(w.done)
	w.watcher.Close()
	close(w.Events)
}

func (w *Watcher) eventLoop() {
	for {
		select {
		case <-w.done:
			return
		case event, ok := <-w.watcher.Events:
			if !ok {
				return
			}

			// Handle directory creation - add to watch
			if event.Op&fsnotify.Create != 0 {
				info, err := os.Stat(event.Name)
				if err == nil && info.IsDir() {
					// Check exclusions
					excluded := false
					for _, excl := range w.cfg.Watch.Exclude {
						if strings.Contains(event.Name, excl) {
							excluded = true
							break
						}
					}
					if !excluded {
						w.watcher.Add(event.Name)
						log.Printf("[watcher] Added directory: %s", event.Name)
					}
					continue
				}
			}

			// Handle file create/write
			if event.Op&(fsnotify.Create|fsnotify.Write) != 0 {
				if w.isWatchedFile(event.Name) {
					w.pendingMu.Lock()
					if _, exists := w.pending[event.Name]; !exists {
						w.pending[event.Name] = time.Now()
						log.Printf("[watcher] File detected, starting stability check: %s", event.Name)
					} else {
						// File was modified again, reset timer
						w.pending[event.Name] = time.Now()
					}
					w.pendingMu.Unlock()
				}
			}

		case err, ok := <-w.watcher.Errors:
			if !ok {
				return
			}
			log.Printf("[watcher] Error: %v", err)
		}
	}
}

func (w *Watcher) stabilityChecker() {
	ticker := time.NewTicker(10 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-w.done:
			return
		case <-ticker.C:
			w.checkStability()
		}
	}
}

func (w *Watcher) checkStability() {
	w.pendingMu.Lock()
	defer w.pendingMu.Unlock()

	stabilityDuration := time.Duration(w.cfg.Watch.StabilitySeconds) * time.Second
	now := time.Now()

	for path, firstSeen := range w.pending {
		// Check if file has been stable long enough
		if now.Sub(firstSeen) < stabilityDuration {
			continue
		}

		// Get current file info
		info, err := os.Stat(path)
		if err != nil {
			// File was deleted
			delete(w.pending, path)
			continue
		}

		// Calculate hash
		hash, err := hashFile(path)
		if err != nil {
			log.Printf("[watcher] Error hashing file %s: %v", path, err)
			delete(w.pending, path)
			continue
		}

		// Emit event
		event := FileEvent{
			Path:    path,
			Hash:    hash,
			Size:    info.Size(),
			ModTime: info.ModTime(),
		}

		select {
		case w.Events <- event:
			log.Printf("[watcher] File stable, emitting event: %s", path)
		default:
			log.Printf("[watcher] Event channel full, dropping: %s", path)
		}

		delete(w.pending, path)
	}
}

func (w *Watcher) isWatchedFile(path string) bool {
	// Check exclusions
	for _, excl := range w.cfg.Watch.Exclude {
		if strings.Contains(path, excl) {
			return false
		}
	}

	// Check extension
	ext := strings.ToLower(filepath.Ext(path))
	for _, watchExt := range w.cfg.Watch.Extensions {
		if ext == strings.ToLower(watchExt) {
			return true
		}
	}

	return false
}

func hashFile(path string) (string, error) {
	f, err := os.Open(path)
	if err != nil {
		return "", err
	}
	defer f.Close()

	h := sha256.New()
	if _, err := io.Copy(h, f); err != nil {
		return "", err
	}

	return hex.EncodeToString(h.Sum(nil)), nil
}

// HashFile exports the hash function for use by other packages
func HashFile(path string) (string, error) {
	return hashFile(path)
}
