package tests

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

	"github.com/xeipuuv/gojsonschema"
)

// MessageTypes defines the three core BACKBEAT interfaces
const (
	BeatFrameType   = "backbeat.beatframe.v1"
	StatusClaimType = "backbeat.statusclaim.v1" 
	BarReportType   = "backbeat.barreport.v1"
)

// BeatFrame represents INT-A: Pulse → All Services
type BeatFrame struct {
	Type      string    `json:"type"`
	ClusterID string    `json:"cluster_id"`
	BeatIndex int64     `json:"beat_index"`
	Downbeat  bool      `json:"downbeat"`
	Phase     string    `json:"phase"`
	HLC       string    `json:"hlc"`
	DeadlineAt time.Time `json:"deadline_at"`
	TempoBPM  float64   `json:"tempo_bpm"`
	WindowID  string    `json:"window_id"`
	Metadata  map[string]interface{} `json:"metadata,omitempty"`
}

// StatusClaim represents INT-B: Agents → Reverb  
type StatusClaim struct {
	Type         string                 `json:"type"`
	AgentID      string                 `json:"agent_id"`
	TaskID       string                 `json:"task_id,omitempty"`
	BeatIndex    int64                  `json:"beat_index"`
	State        string                 `json:"state"`
	BeatsLeft    int                    `json:"beats_left,omitempty"`
	Progress     float64                `json:"progress,omitempty"`
	Notes        string                 `json:"notes,omitempty"`
	HLC          string                 `json:"hlc"`
	Resources    map[string]interface{} `json:"resources,omitempty"`
	Dependencies []string               `json:"dependencies,omitempty"`
	Metadata     map[string]interface{} `json:"metadata,omitempty"`
}

// BarReport represents INT-C: Reverb → All Services
type BarReport struct {
	Type                    string                   `json:"type"`
	WindowID                string                   `json:"window_id"`
	FromBeat                int64                    `json:"from_beat"`
	ToBeat                  int64                    `json:"to_beat"`
	AgentsReporting         int                      `json:"agents_reporting"`
	OnTimeReviews           int                      `json:"on_time_reviews"`
	HelpPromisesFulfilled   int                      `json:"help_promises_fulfilled"`
	SecretRotationsOK       bool                     `json:"secret_rotations_ok"`
	TempoDriftMS            float64                  `json:"tempo_drift_ms"`
	Issues                  []map[string]interface{} `json:"issues,omitempty"`
	Performance             map[string]interface{}   `json:"performance,omitempty"`
	HealthIndicators        map[string]interface{}   `json:"health_indicators,omitempty"`
	Metadata                map[string]interface{}   `json:"metadata,omitempty"`
}

// TestSchemaValidation tests that all JSON schemas are valid and messages conform
func TestSchemaValidation(t *testing.T) {
	schemaDir := "../schemas"
	
	tests := []struct {
		name       string
		schemaFile string
		validMsgs  []interface{}
		invalidMsgs []map[string]interface{}
	}{
		{
			name:       "BeatFrame Schema Validation",
			schemaFile: "beatframe-v1.schema.json",
			validMsgs: []interface{}{
				BeatFrame{
					Type:      BeatFrameType,
					ClusterID: "test-cluster",
					BeatIndex: 100,
					Downbeat:  false,
					Phase:     "execute",
					HLC:       "7ffd:0001:abcd",
					DeadlineAt: time.Now().Add(30 * time.Second),
					TempoBPM:  2.0,
					WindowID:  "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5",
				},
				BeatFrame{
					Type:      BeatFrameType,
					ClusterID: "prod",
					BeatIndex: 0,
					Downbeat:  true,
					Phase:     "plan",
					HLC:       "0001:0000:cafe",
					DeadlineAt: time.Now().Add(15 * time.Second),
					TempoBPM:  4.0,
					WindowID:  "a1b2c3d4e5f6789012345678901234ab",
					Metadata: map[string]interface{}{
						"pulse_version": "1.0.0",
						"cluster_health": "healthy",
					},
				},
			},
			invalidMsgs: []map[string]interface{}{
				// Missing required fields
				{
					"type":       BeatFrameType,
					"cluster_id": "test",
					// missing beat_index, downbeat, phase, etc.
				},
				// Invalid phase
				{
					"type":        BeatFrameType,
					"cluster_id":  "test",
					"beat_index":  0,
					"downbeat":    false,
					"phase":       "invalid_phase",
					"hlc":         "7ffd:0001:abcd",
					"deadline_at": "2025-09-05T12:00:00Z",
					"tempo_bpm":   2.0,
					"window_id":   "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5",
				},
				// Invalid HLC format
				{
					"type":        BeatFrameType,
					"cluster_id":  "test",
					"beat_index":  0,
					"downbeat":    false,
					"phase":       "plan",
					"hlc":         "invalid-hlc-format",
					"deadline_at": "2025-09-05T12:00:00Z",
					"tempo_bpm":   2.0,
					"window_id":   "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5",
				},
			},
		},
		{
			name:       "StatusClaim Schema Validation",
			schemaFile: "statusclaim-v1.schema.json",
			validMsgs: []interface{}{
				StatusClaim{
					Type:      StatusClaimType,
					AgentID:   "worker:test-01",
					TaskID:    "task:123",
					BeatIndex: 100,
					State:     "executing",
					BeatsLeft: 3,
					Progress:  0.5,
					Notes:     "processing batch",
					HLC:       "7ffd:0001:beef",
				},
				StatusClaim{
					Type:      StatusClaimType,
					AgentID:   "agent:backup",
					BeatIndex: 101,
					State:     "idle",
					HLC:       "7ffe:0002:dead",
					Resources: map[string]interface{}{
						"cpu_percent": 25.0,
						"memory_mb":   512,
					},
				},
			},
			invalidMsgs: []map[string]interface{}{
				// Missing required fields
				{
					"type":     StatusClaimType,
					"agent_id": "test",
					// missing beat_index, state, hlc
				},
				// Invalid state
				{
					"type":       StatusClaimType,
					"agent_id":   "test",
					"beat_index": 0,
					"state":      "invalid_state",
					"hlc":        "7ffd:0001:abcd",
				},
				// Negative progress
				{
					"type":       StatusClaimType,
					"agent_id":   "test",
					"beat_index": 0,
					"state":      "executing",
					"progress":   -0.1,
					"hlc":        "7ffd:0001:abcd",
				},
			},
		},
		{
			name:       "BarReport Schema Validation",
			schemaFile: "barreport-v1.schema.json",
			validMsgs: []interface{}{
				BarReport{
					Type:                  BarReportType,
					WindowID:              "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5",
					FromBeat:              0,
					ToBeat:                119,
					AgentsReporting:       150,
					OnTimeReviews:         147,
					HelpPromisesFulfilled: 12,
					SecretRotationsOK:     true,
					TempoDriftMS:          -2.1,
				},
				BarReport{
					Type:                  BarReportType,
					WindowID:              "a1b2c3d4e5f6789012345678901234ab",
					FromBeat:              120,
					ToBeat:                239,
					AgentsReporting:       200,
					OnTimeReviews:         195,
					HelpPromisesFulfilled: 25,
					SecretRotationsOK:     false,
					TempoDriftMS:          15.7,
					Issues: []map[string]interface{}{
						{
							"severity":    "warning",
							"category":    "timing",
							"count":       5,
							"description": "Some agents running late",
						},
					},
				},
			},
			invalidMsgs: []map[string]interface{}{
				// Missing required fields
				{
					"type":      BarReportType,
					"window_id": "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5",
					// missing from_beat, to_beat, etc.
				},
				// Invalid window_id format
				{
					"type":                    BarReportType,
					"window_id":               "invalid-window-id",
					"from_beat":               0,
					"to_beat":                 119,
					"agents_reporting":        150,
					"on_time_reviews":         147,
					"help_promises_fulfilled": 12,
					"secret_rotations_ok":     true,
					"tempo_drift_ms":          0.0,
				},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Load schema
			schemaPath := filepath.Join(schemaDir, tt.schemaFile)
			schemaLoader := gojsonschema.NewReferenceLoader("file://" + schemaPath)
			
			// Test valid messages
			for i, validMsg := range tt.validMsgs {
				t.Run(fmt.Sprintf("Valid_%d", i), func(t *testing.T) {
					msgBytes, err := json.Marshal(validMsg)
					if err != nil {
						t.Fatalf("Failed to marshal valid message: %v", err)
					}
					
					docLoader := gojsonschema.NewBytesLoader(msgBytes)
					result, err := gojsonschema.Validate(schemaLoader, docLoader)
					if err != nil {
						t.Fatalf("Schema validation failed: %v", err)
					}
					
					if !result.Valid() {
						t.Errorf("Valid message failed validation: %v", result.Errors())
					}
				})
			}
			
			// Test invalid messages
			for i, invalidMsg := range tt.invalidMsgs {
				t.Run(fmt.Sprintf("Invalid_%d", i), func(t *testing.T) {
					msgBytes, err := json.Marshal(invalidMsg)
					if err != nil {
						t.Fatalf("Failed to marshal invalid message: %v", err)
					}
					
					docLoader := gojsonschema.NewBytesLoader(msgBytes)
					result, err := gojsonschema.Validate(schemaLoader, docLoader)
					if err != nil {
						t.Fatalf("Schema validation failed: %v", err)
					}
					
					if result.Valid() {
						t.Errorf("Invalid message passed validation when it should have failed")
					}
				})
			}
		})
	}
}

// TestMessageParsing tests that messages can be correctly parsed from JSON
func TestMessageParsing(t *testing.T) {
	tests := []struct {
		name     string
		jsonStr  string
		expected interface{}
	}{
		{
			name: "Parse BeatFrame",
			jsonStr: `{
				"type": "backbeat.beatframe.v1",
				"cluster_id": "test",
				"beat_index": 123,
				"downbeat": true,
				"phase": "review",
				"hlc": "7ffd:0001:abcd",
				"deadline_at": "2025-09-05T12:00:00Z",
				"tempo_bpm": 2.5,
				"window_id": "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5"
			}`,
			expected: BeatFrame{
				Type:      BeatFrameType,
				ClusterID: "test",
				BeatIndex: 123,
				Downbeat:  true,
				Phase:     "review",
				HLC:       "7ffd:0001:abcd",
				TempoBPM:  2.5,
				WindowID:  "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5",
			},
		},
		{
			name: "Parse StatusClaim",
			jsonStr: `{
				"type": "backbeat.statusclaim.v1",
				"agent_id": "worker:01",
				"beat_index": 456,
				"state": "completed",
				"progress": 1.0,
				"hlc": "7ffe:0002:beef"
			}`,
			expected: StatusClaim{
				Type:      StatusClaimType,
				AgentID:   "worker:01",
				BeatIndex: 456,
				State:     "completed",
				Progress:  1.0,
				HLC:       "7ffe:0002:beef",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			switch expected := tt.expected.(type) {
			case BeatFrame:
				var parsed BeatFrame
				err := json.Unmarshal([]byte(tt.jsonStr), &parsed)
				if err != nil {
					t.Fatalf("Failed to parse BeatFrame: %v", err)
				}
				
				if parsed.Type != expected.Type ||
					parsed.ClusterID != expected.ClusterID ||
					parsed.BeatIndex != expected.BeatIndex {
					t.Errorf("Parsed BeatFrame doesn't match expected")
				}
				
			case StatusClaim:
				var parsed StatusClaim
				err := json.Unmarshal([]byte(tt.jsonStr), &parsed)
				if err != nil {
					t.Fatalf("Failed to parse StatusClaim: %v", err)
				}
				
				if parsed.Type != expected.Type ||
					parsed.AgentID != expected.AgentID ||
					parsed.State != expected.State {
					t.Errorf("Parsed StatusClaim doesn't match expected")
				}
			}
		})
	}
}

// TestHLCValidation tests Hybrid Logical Clock format validation
func TestHLCValidation(t *testing.T) {
	validHLCs := []string{
		"0000:0000:0000",
		"7ffd:0001:abcd", 
		"FFFF:FFFF:FFFF",
		"1234:5678:90ab",
	}
	
	invalidHLCs := []string{
		"invalid",
		"7ffd:0001",        // too short
		"7ffd:0001:abcd:ef", // too long
		"gggg:0001:abcd",   // invalid hex
		"7ffd:0001:abcdz",  // invalid hex
	}
	
	for _, hlc := range validHLCs {
		t.Run(fmt.Sprintf("Valid_%s", hlc), func(t *testing.T) {
			if !isValidHLC(hlc) {
				t.Errorf("Valid HLC %s was rejected", hlc)
			}
		})
	}
	
	for _, hlc := range invalidHLCs {
		t.Run(fmt.Sprintf("Invalid_%s", hlc), func(t *testing.T) {
			if isValidHLC(hlc) {
				t.Errorf("Invalid HLC %s was accepted", hlc)
			}
		})
	}
}

// TestWindowIDValidation tests window ID format validation
func TestWindowIDValidation(t *testing.T) {
	validWindowIDs := []string{
		"7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5",
		"a1b2c3d4e5f6789012345678901234ab",
		"00000000000000000000000000000000",
		"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
	}
	
	invalidWindowIDs := []string{
		"invalid",
		"7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d", // too short
		"7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d55", // too long
		"7e9b0e6c4c9a4e59b7f2d9a3c1b2e4g5", // invalid hex
	}
	
	for _, windowID := range validWindowIDs {
		t.Run(fmt.Sprintf("Valid_%s", windowID), func(t *testing.T) {
			if !isValidWindowID(windowID) {
				t.Errorf("Valid window ID %s was rejected", windowID)
			}
		})
	}
	
	for _, windowID := range invalidWindowIDs {
		t.Run(fmt.Sprintf("Invalid_%s", windowID), func(t *testing.T) {
			if isValidWindowID(windowID) {
				t.Errorf("Invalid window ID %s was accepted", windowID)
			}
		})
	}
}

// Helper functions for validation
func isValidHLC(hlc string) bool {
	parts := strings.Split(hlc, ":")
	if len(parts) != 3 {
		return false
	}
	
	for _, part := range parts {
		if len(part) != 4 {
			return false
		}
		for _, char := range part {
			if !((char >= '0' && char <= '9') || (char >= 'a' && char <= 'f') || (char >= 'A' && char <= 'F')) {
				return false
			}
		}
	}
	return true
}

func isValidWindowID(windowID string) bool {
	if len(windowID) != 32 {
		return false
	}
	
	for _, char := range windowID {
		if !((char >= '0' && char <= '9') || (char >= 'a' && char <= 'f') || (char >= 'A' && char <= 'F')) {
			return false
		}
	}
	return true
}

// BenchmarkSchemaValidation benchmarks schema validation performance
func BenchmarkSchemaValidation(b *testing.B) {
	schemaDir := "../schemas"
	schemaPath := filepath.Join(schemaDir, "beatframe-v1.schema.json")
	schemaLoader := gojsonschema.NewReferenceLoader("file://" + schemaPath)
	
	beatFrame := BeatFrame{
		Type:      BeatFrameType,
		ClusterID: "benchmark",
		BeatIndex: 1000,
		Downbeat:  false,
		Phase:     "execute",
		HLC:       "7ffd:0001:abcd",
		DeadlineAt: time.Now().Add(30 * time.Second),
		TempoBPM:  2.0,
		WindowID:  "7e9b0e6c4c9a4e59b7f2d9a3c1b2e4d5",
	}
	
	msgBytes, _ := json.Marshal(beatFrame)
	docLoader := gojsonschema.NewBytesLoader(msgBytes)
	
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		result, err := gojsonschema.Validate(schemaLoader, docLoader)
		if err != nil || !result.Valid() {
			b.Fatal("Validation failed")
		}
	}
}

// Helper function to check if schema files exist
func TestSchemaFilesExist(t *testing.T) {
	schemaDir := "../schemas"
	requiredSchemas := []string{
		"beatframe-v1.schema.json",
		"statusclaim-v1.schema.json", 
		"barreport-v1.schema.json",
	}
	
	for _, schema := range requiredSchemas {
		schemaPath := filepath.Join(schemaDir, schema)
		if _, err := os.Stat(schemaPath); os.IsNotExist(err) {
			t.Errorf("Required schema file %s does not exist", schemaPath)
		}
	}
}