// Package audit provides tamper-evident audit logging with hash chaining
package audit

import (
	"bufio"
	"encoding/json"
	"fmt"
	"os"
	"sync"
	"time"

	"github.com/jfraeys/fetch_ml/internal/fileutil"
)

// StateEntry represents a sealed checkpoint entry
type StateEntry struct {
	Seq       uint64    `json:"seq"`
	Hash      string    `json:"hash"`
	Timestamp time.Time `json:"ts"`
	Type      string    `json:"type"`
}

// SealedStateManager maintains tamper-evident state checkpoints.
// It writes to an append-only chain file and an overwritten current file.
// The chain file is fsynced before returning to ensure crash safety.
type SealedStateManager struct {
	chainFile   string
	currentFile string
	mu          sync.Mutex
}

// NewSealedStateManager creates a new sealed state manager
func NewSealedStateManager(chainFile, currentFile string) *SealedStateManager {
	return &SealedStateManager{
		chainFile:   chainFile,
		currentFile: currentFile,
	}
}

// Checkpoint writes current state to sealed files.
// It writes to the append-only chain file first, fsyncs it, then overwrites the current file.
// This ordering ensures crash safety: the chain file is always the source of truth.
func (ssm *SealedStateManager) Checkpoint(seq uint64, hash string) error {
	ssm.mu.Lock()
	defer ssm.mu.Unlock()

	entry := StateEntry{
		Seq:       seq,
		Hash:      hash,
		Timestamp: time.Now().UTC(),
		Type:      "fsync",
	}
	data, err := json.Marshal(entry)
	if err != nil {
		return fmt.Errorf("marshal state entry: %w", err)
	}

	// Write to append-only chain file first
	f, err := os.OpenFile(ssm.chainFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600)
	if err != nil {
		return fmt.Errorf("open chain file: %w", err)
	}

	if _, err := f.Write(append(data, '\n')); err != nil {
		f.Close()
		return fmt.Errorf("write chain entry: %w", err)
	}

	// CRITICAL: fsync chain before returning — crash safety
	if err := f.Sync(); err != nil {
		f.Close()
		return fmt.Errorf("sync sealed chain: %w", err)
	}

	if err := f.Close(); err != nil {
		return fmt.Errorf("close chain file: %w", err)
	}

	// Overwrite current-state file (fast lookup) with crash safety (fsync)
	if err := fileutil.WriteFileSafe(ssm.currentFile, data, 0o600); err != nil {
		return fmt.Errorf("write current file: %w", err)
	}

	return nil
}

// RecoverState reads last valid state from sealed files.
// It tries the current file first (fast path), then falls back to scanning the chain file.
func (ssm *SealedStateManager) RecoverState() (uint64, string, error) {
	// Try current file first (fast path)
	data, err := os.ReadFile(ssm.currentFile)
	if err == nil {
		var entry StateEntry
		if json.Unmarshal(data, &entry) == nil {
			return entry.Seq, entry.Hash, nil
		}
	}

	// Fall back to scanning chain file for last valid entry
	return ssm.scanChainFileForLastValid()
}

// scanChainFileForLastValid scans the chain file and returns the last valid entry
func (ssm *SealedStateManager) scanChainFileForLastValid() (uint64, string, error) {
	f, err := os.Open(ssm.chainFile)
	if err != nil {
		if os.IsNotExist(err) {
			return 0, "", nil
		}
		return 0, "", fmt.Errorf("open chain file: %w", err)
	}
	defer f.Close()

	var lastEntry StateEntry
	scanner := bufio.NewScanner(f)
	lineNum := 0
	for scanner.Scan() {
		lineNum++
		line := scanner.Text()
		if line == "" {
			continue
		}

		var entry StateEntry
		if err := json.Unmarshal([]byte(line), &entry); err != nil {
			// Corrupted line - log but continue
			continue
		}
		lastEntry = entry
	}

	if err := scanner.Err(); err != nil {
		return 0, "", fmt.Errorf("scan chain file: %w", err)
	}

	return lastEntry.Seq, lastEntry.Hash, nil
}

// VerifyChainIntegrity checks that the chain file is intact and returns the number of valid entries
func (ssm *SealedStateManager) VerifyChainIntegrity() (int, error) {
	f, err := os.Open(ssm.chainFile)
	if err != nil {
		if os.IsNotExist(err) {
			return 0, nil
		}
		return 0, fmt.Errorf("open chain file: %w", err)
	}
	defer f.Close()

	validCount := 0
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := scanner.Text()
		if line == "" {
			continue
		}

		var entry StateEntry
		if err := json.Unmarshal([]byte(line), &entry); err != nil {
			continue // Skip corrupted lines
		}
		validCount++
	}

	if err := scanner.Err(); err != nil {
		return validCount, fmt.Errorf("scan chain file: %w", err)
	}

	return validCount, nil
}

// Close is a no-op for SealedStateManager (state is written immediately)
func (ssm *SealedStateManager) Close() error {
	return nil
}
