package face

import (
	"context"
	"fmt"
	"sync"
	"time"
)

// Client defines the interface for face detection, indexing, and search operations
type Client interface {
	// CreateCollection creates a new face collection for a space
	CreateCollection(ctx context.Context, collectionName string) error

	// DeleteCollection deletes a face collection
	DeleteCollection(ctx context.Context, collectionName string) error

	// DetectFaces detects faces in an image
	DetectFaces(ctx context.Context, imageBytes []byte) ([]FaceDetection, error)

	// IndexFace indexes a face in a collection
	IndexFace(ctx context.Context, imageBytes []byte, photoID string) (*FaceDetection, error)

	// SearchFacesByImage searches for similar faces in the collection
	SearchFacesByImage(ctx context.Context, imageBytes []byte, maxFaces int, threshold float64) ([]FaceMatch, error)
}

// Compile-time check that RekognitionClient implements Client
var _ Client = (*RekognitionClient)(nil)

// MockClient is a mock implementation of the Client interface for testing
type MockClient struct {
	mu            sync.RWMutex
	collections   map[string]*CollectionInfo
	faces         map[string][]FaceIndex
	detections    []FaceDetection
	searchResults []FaceMatch

	// Mock configuration
	DetectFacesFunc    func(ctx context.Context, imageBytes []byte) ([]FaceDetection, error)
	IndexFaceFunc      func(ctx context.Context, imageBytes []byte, photoID string) (*FaceDetection, error)
	SearchFacesFunc    func(ctx context.Context, imageBytes []byte, maxFaces int, threshold float64) ([]FaceMatch, error)
}

// FaceIndex represents an indexed face (internal to mock)
type FaceIndex struct {
	FaceID          string     `json:"faceId"`
	ExternalImageID string     `json:"externalImageId"`
	Confidence      float64    `json:"confidence"`
	BoundingBox     BoundingBox `json:"boundingBox"`
	IndexedAt       time.Time  `json:"indexedAt"`
}

// NewMockClient creates a new mock client for testing
func NewMockClient() *MockClient {
	return &MockClient{
		collections: make(map[string]*CollectionInfo),
		faces:       make(map[string][]FaceIndex),
	}
}

// CreateCollection creates a mock collection
func (m *MockClient) CreateCollection(ctx context.Context, collectionName string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	m.collections[collectionName] = &CollectionInfo{
		CollectionID: collectionName,
		FaceCount:    0,
		CreationDate: time.Now(),
		LastUpdated:  time.Now(),
	}
	return nil
}

// DeleteCollection deletes a mock collection
func (m *MockClient) DeleteCollection(ctx context.Context, collectionName string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	delete(m.collections, collectionName)
	delete(m.faces, collectionName)
	return nil
}

// DetectFaces returns mock face detections
func (m *MockClient) DetectFaces(ctx context.Context, imageBytes []byte) ([]FaceDetection, error) {
	if m.DetectFacesFunc != nil {
		return m.DetectFacesFunc(ctx, imageBytes)
	}

	m.mu.RLock()
	defer m.mu.RUnlock()

	// Return mock detections
	if len(m.detections) > 0 {
		return m.detections, nil
	}

	// Default: return one face
	return []FaceDetection{
		{
			Confidence: 99.5,
			BoundingBox: BoundingBox{
				Left:   0.1,
				Top:    0.1,
				Width:  0.3,
				Height: 0.3,
			},
		},
	}, nil
}

// IndexFace indexes a mock face
func (m *MockClient) IndexFace(ctx context.Context, imageBytes []byte, photoID string) (*FaceDetection, error) {
	if m.IndexFaceFunc != nil {
		return m.IndexFaceFunc(ctx, imageBytes, photoID)
	}

	m.mu.Lock()
	defer m.mu.Unlock()

	detection := &FaceDetection{
		Confidence: 99.5,
		BoundingBox: BoundingBox{
			Left:   0.1,
			Top:    0.1,
			Width:  0.3,
			Height: 0.3,
		},
	}

	// Store in mock database
	faceIndex := FaceIndex{
		FaceID:          fmt.Sprintf("face-%s-%d", photoID, time.Now().UnixNano()),
		ExternalImageID: photoID,
		Confidence:      99.5,
		BoundingBox:     detection.BoundingBox,
		IndexedAt:       time.Now(),
	}
	m.faces["hatch-default"] = append(m.faces["hatch-default"], faceIndex)

	return detection, nil
}

// SearchFacesByImage returns mock search results
func (m *MockClient) SearchFacesByImage(ctx context.Context, imageBytes []byte, maxFaces int, threshold float64) ([]FaceMatch, error) {
	if m.SearchFacesFunc != nil {
		return m.SearchFacesFunc(ctx, imageBytes, maxFaces, threshold)
	}

	m.mu.RLock()
	defer m.mu.RUnlock()

	// Return mock matches
	if len(m.searchResults) > 0 {
		if maxFaces > 0 && len(m.searchResults) > maxFaces {
			return m.searchResults[:maxFaces], nil
		}
		return m.searchResults, nil
	}

	// Default: return one match
	return []FaceMatch{
		{
			FaceID:          "face-mock-1",
			ExternalImageID: "photo-1",
			Similarity:      95.0,
			BoundingBox: BoundingBox{
				Left:   0.1,
				Top:    0.1,
				Width:  0.3,
				Height: 0.3,
			},
		},
	}, nil
}

// SetDetections configures mock face detections
func (m *MockClient) SetDetections(detections []FaceDetection) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.detections = detections
}

// SetSearchResults configures mock search results
func (m *MockClient) SetSearchResults(results []FaceMatch) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.searchResults = results
}
