package audit

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

// AuditEvent represents an immutable audit log entry
type AuditEvent struct {
	EventID      string                 `json:"event_id"`
	EventType    string                 `json:"event_type"` // "planner_decision", "judge_verdict", "implementer_change"
	WorkflowID   string                 `json:"workflow_id"`
	TaskID       string                 `json:"task_id"`
	Actor        string                 `json:"actor"` // "planner", "judge", "implementer"
	Timestamp    time.Time              `json:"timestamp"`
	Action       string                 `json:"action"` // Description of what was decided/done
	Reasoning    string                 `json:"reasoning"` // Why this decision was made
	Input        map[string]interface{} `json:"input,omitempty"`
	Output       map[string]interface{} `json:"output,omitempty"`
	Status       string                 `json:"status"` // "success", "failure", "pending"
	Error        string                 `json:"error,omitempty"`
	Metadata     map[string]interface{} `json:"metadata,omitempty"`
}

// AuditLogger logs immutable audit events
type AuditLogger struct {
	mu       sync.Mutex
	basePath string
	logFile  string
}

// NewAuditLogger creates a new audit logger
func NewAuditLogger(basePath string) *AuditLogger {
	return &AuditLogger{
		basePath: basePath,
		logFile:  filepath.Join(basePath, "audit", "audit.jsonl"),
	}
}

// LogEvent logs an audit event (immutable append-only)
func (al *AuditLogger) LogEvent(event *AuditEvent) error {
	if event == nil {
		return fmt.Errorf("event cannot be nil")
	}

	al.mu.Lock()
	defer al.mu.Unlock()

	// Set timestamp if not already set
	if event.Timestamp.IsZero() {
		event.Timestamp = time.Now()
	}

	// Generate event ID if not set
	if event.EventID == "" {
		event.EventID = fmt.Sprintf("%s-%d", event.WorkflowID, event.Timestamp.UnixNano())
	}

	// Create audit directory if it doesn't exist
	if err := os.MkdirAll(filepath.Dir(al.logFile), 0755); err != nil {
		return err
	}

	// Marshal to JSON
	data, err := json.Marshal(event)
	if err != nil {
		return err
	}

	// Append to file (immutable log)
	f, err := os.OpenFile(al.logFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = f.Write(append(data, '\n'))
	if err != nil {
		return err
	}

	return nil
}

// LogPlannerDecision logs a planner decision
func (al *AuditLogger) LogPlannerDecision(workflowID, taskID string, decision string, reasoning string, metadata map[string]interface{}) error {
	event := &AuditEvent{
		EventType: "planner_decision",
		WorkflowID: workflowID,
		TaskID: taskID,
		Actor: "planner",
		Timestamp: time.Now(),
		Action: decision,
		Reasoning: reasoning,
		Status: "success",
		Metadata: metadata,
	}
	return al.LogEvent(event)
}

// LogJudgeVerdict logs a judge verdict
func (al *AuditLogger) LogJudgeVerdict(workflowID, taskID string, verdict string, reasoning string, metadata map[string]interface{}) error {
	event := &AuditEvent{
		EventType: "judge_verdict",
		WorkflowID: workflowID,
		TaskID: taskID,
		Actor: "judge",
		Timestamp: time.Now(),
		Action: verdict,
		Reasoning: reasoning,
		Status: "success",
		Metadata: metadata,
	}
	return al.LogEvent(event)
}

// LogImplementerChange logs an implementer change
func (al *AuditLogger) LogImplementerChange(workflowID, taskID string, changeDesc string, filesModified []string, metadata map[string]interface{}) error {
	output := map[string]interface{}{
		"files_modified": filesModified,
	}

	event := &AuditEvent{
		EventType: "implementer_change",
		WorkflowID: workflowID,
		TaskID: taskID,
		Actor: "implementer",
		Timestamp: time.Now(),
		Action: changeDesc,
		Output: output,
		Status: "success",
		Metadata: metadata,
	}
	return al.LogEvent(event)
}

// QueryByTask retrieves all events for a specific task
func (al *AuditLogger) QueryByTask(taskID string) ([]*AuditEvent, error) {
	al.mu.Lock()
	defer al.mu.Unlock()

	data, err := os.ReadFile(al.logFile)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}

	var events []*AuditEvent
	var inLine []byte

	for _, ch := range data {
		if ch == '\n' {
			if len(inLine) > 0 {
				var event AuditEvent
				if err := json.Unmarshal(inLine, &event); err == nil {
					if event.TaskID == taskID {
						events = append(events, &event)
					}
				}
			}
			inLine = nil
		} else {
			inLine = append(inLine, ch)
		}
	}

	return events, nil
}

// QueryByWorkflow retrieves all events for a specific workflow
func (al *AuditLogger) QueryByWorkflow(workflowID string) ([]*AuditEvent, error) {
	al.mu.Lock()
	defer al.mu.Unlock()

	data, err := os.ReadFile(al.logFile)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}

	var events []*AuditEvent
	var inLine []byte

	for _, ch := range data {
		if ch == '\n' {
			if len(inLine) > 0 {
				var event AuditEvent
				if err := json.Unmarshal(inLine, &event); err == nil {
					if event.WorkflowID == workflowID {
						events = append(events, &event)
					}
				}
			}
			inLine = nil
		} else {
			inLine = append(inLine, ch)
		}
	}

	return events, nil
}

// QueryByActor retrieves all events by a specific actor
func (al *AuditLogger) QueryByActor(actor string) ([]*AuditEvent, error) {
	al.mu.Lock()
	defer al.mu.Unlock()

	data, err := os.ReadFile(al.logFile)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}

	var events []*AuditEvent
	var inLine []byte

	for _, ch := range data {
		if ch == '\n' {
			if len(inLine) > 0 {
				var event AuditEvent
				if err := json.Unmarshal(inLine, &event); err == nil {
					if event.Actor == actor {
						events = append(events, &event)
					}
				}
			}
			inLine = nil
		} else {
			inLine = append(inLine, ch)
		}
	}

	return events, nil
}

// QueryByTimeRange retrieves events within a time range
func (al *AuditLogger) QueryByTimeRange(start, end time.Time) ([]*AuditEvent, error) {
	al.mu.Lock()
	defer al.mu.Unlock()

	data, err := os.ReadFile(al.logFile)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}

	var events []*AuditEvent
	var inLine []byte

	for _, ch := range data {
		if ch == '\n' {
			if len(inLine) > 0 {
				var event AuditEvent
				if err := json.Unmarshal(inLine, &event); err == nil {
					if event.Timestamp.After(start) && event.Timestamp.Before(end) {
						events = append(events, &event)
					}
				}
			}
			inLine = nil
		} else {
			inLine = append(inLine, ch)
		}
	}

	return events, nil
}

// GetAuditTrail retrieves the full audit trail
func (al *AuditLogger) GetAuditTrail() ([]*AuditEvent, error) {
	al.mu.Lock()
	defer al.mu.Unlock()

	data, err := os.ReadFile(al.logFile)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}

	var events []*AuditEvent
	var inLine []byte

	for _, ch := range data {
		if ch == '\n' {
			if len(inLine) > 0 {
				var event AuditEvent
				if err := json.Unmarshal(inLine, &event); err == nil {
					events = append(events, &event)
				}
			}
			inLine = nil
		} else {
			inLine = append(inLine, ch)
		}
	}

	return events, nil
}

// GetEventCount returns the total number of audit events
func (al *AuditLogger) GetEventCount() (int, error) {
	events, err := al.GetAuditTrail()
	if err != nil {
		return 0, err
	}
	return len(events), nil
}
