package lockfile

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

// LockInfo represents the metadata stored in the daemon.lock file
type LockInfo struct {
	PID       int       `json:"pid"`
	ParentPID int       `json:"parent_pid,omitempty"`
	Database  string    `json:"database"`
	Version   string    `json:"version"`
	StartedAt time.Time `json:"started_at"`
}

// TryDaemonLock attempts to acquire and immediately release the daemon lock
// to check if a daemon is running. Returns true if daemon is running.
// Falls back to PID file check for backward compatibility with pre-lock daemons.
//
// This is a cheap probe operation that should be called before attempting
// RPC connections to avoid unnecessary connection timeouts.
func TryDaemonLock(beadsDir string) (running bool, pid int) {
	lockPath := filepath.Join(beadsDir, "daemon.lock")

	// Open lock file with read-write access (required for LockFileEx on Windows)
	// #nosec G304 - controlled path from config
	f, err := os.OpenFile(lockPath, os.O_RDWR, 0)
	if err != nil {
		// No lock file - could be old daemon without lock support
		// Fall back to PID file check for backward compatibility
		return checkPIDFile(beadsDir)
	}
	defer func() { _ = f.Close() }()

	// Try to acquire lock non-blocking
	if err := flockExclusive(f); err != nil {
		if err == errDaemonLocked {
			// Lock is held - daemon is running
			// Try to read PID from JSON format (best effort)
			_, _ = f.Seek(0, 0)
			var lockInfo LockInfo
			if err := json.NewDecoder(f).Decode(&lockInfo); err == nil {
				pid = lockInfo.PID
			} else {
				// Fallback: try reading as plain integer (old format)
				_, _ = f.Seek(0, 0)
				data := make([]byte, 32)
				n, _ := f.Read(data)
				if n > 0 {
					_, _ = fmt.Sscanf(string(data[:n]), "%d", &pid)
				}
				// Fallback to PID file if we couldn't read PID from lock file
				if pid == 0 {
					_, pid = checkPIDFile(beadsDir)
				}
			}
			return true, pid
		}
		// Other errors mean we can't determine status
		return false, 0
	}

	// We got the lock - no daemon running
	// Release immediately (file close will do this)
	return false, 0
}

// checkPIDFile checks if a daemon is running by reading the PID file.
// This is used for backward compatibility with pre-lock daemons.
func checkPIDFile(beadsDir string) (running bool, pid int) {
	pidFile := filepath.Join(beadsDir, "daemon.pid")
	// #nosec G304 - controlled path from config
	data, err := os.ReadFile(pidFile)
	if err != nil {
		return false, 0
	}

	pidVal, err := strconv.Atoi(strings.TrimSpace(string(data)))
	if err != nil {
		return false, 0
	}

	if !isProcessRunning(pidVal) {
		return false, 0
	}

	return true, pidVal
}

// ReadLockInfo reads and parses the daemon lock file
// Returns lock info if available, or error if file doesn't exist or can't be parsed
func ReadLockInfo(beadsDir string) (*LockInfo, error) {
	lockPath := filepath.Join(beadsDir, "daemon.lock")
	
	// #nosec G304 - controlled path from config
	data, err := os.ReadFile(lockPath)
	if err != nil {
		return nil, err
	}
	
	var lockInfo LockInfo
	if err := json.Unmarshal(data, &lockInfo); err != nil {
		// Try parsing as old format (plain PID)
		var pid int
		if _, err := fmt.Sscanf(string(data), "%d", &pid); err == nil {
			return &LockInfo{PID: pid}, nil
		}
		return nil, fmt.Errorf("cannot parse lock file: %w", err)
	}
	
	return &lockInfo, nil
}
