package postgres

import (
	"context"
	"database/sql"
	"encoding/json"

	"github.com/google/uuid"
	"github.com/rwadurian/mpc-system/services/account/domain/repositories"
)

// SessionEventPostgresRepo implements SessionEventRepository using PostgreSQL
// This is an append-only repository - events are never updated or deleted
type SessionEventPostgresRepo struct {
	db *sql.DB
}

// NewSessionEventPostgresRepo creates a new SessionEventPostgresRepo
func NewSessionEventPostgresRepo(db *sql.DB) repositories.SessionEventRepository {
	return &SessionEventPostgresRepo{db: db}
}

// Create inserts a new event (append-only, never update)
func (r *SessionEventPostgresRepo) Create(ctx context.Context, event *repositories.SessionEvent) error {
	query := `
		INSERT INTO session_events (
			id, session_id, username, event_type, session_type,
			threshold_n, threshold_t, party_id, party_index,
			message_hash, public_key, signature, error_message,
			metadata, created_at
		) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
	`

	var metadataJSON []byte
	var err error
	if event.Metadata != nil {
		metadataJSON, err = json.Marshal(event.Metadata)
		if err != nil {
			return err
		}
	}

	_, err = r.db.ExecContext(ctx, query,
		event.ID,
		event.SessionID,
		event.Username,
		string(event.EventType),
		string(event.SessionType),
		event.ThresholdN,
		event.ThresholdT,
		event.PartyID,
		event.PartyIndex,
		event.MessageHash,
		event.PublicKey,
		event.Signature,
		event.ErrorMessage,
		metadataJSON,
		event.CreatedAt,
	)

	return err
}

// GetBySessionID retrieves all events for a session, ordered by creation time
func (r *SessionEventPostgresRepo) GetBySessionID(ctx context.Context, sessionID uuid.UUID) ([]*repositories.SessionEvent, error) {
	query := `
		SELECT id, session_id, username, event_type, session_type,
			threshold_n, threshold_t, party_id, party_index,
			message_hash, public_key, signature, error_message,
			metadata, created_at
		FROM session_events
		WHERE session_id = $1
		ORDER BY created_at ASC
	`

	rows, err := r.db.QueryContext(ctx, query, sessionID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return r.scanEvents(rows)
}

// GetByUsername retrieves all events for a user, ordered by creation time desc
func (r *SessionEventPostgresRepo) GetByUsername(ctx context.Context, username string, limit int) ([]*repositories.SessionEvent, error) {
	query := `
		SELECT id, session_id, username, event_type, session_type,
			threshold_n, threshold_t, party_id, party_index,
			message_hash, public_key, signature, error_message,
			metadata, created_at
		FROM session_events
		WHERE username = $1
		ORDER BY created_at DESC
		LIMIT $2
	`

	rows, err := r.db.QueryContext(ctx, query, username, limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return r.scanEvents(rows)
}

// GetLatestBySessionID retrieves the most recent event for a session
func (r *SessionEventPostgresRepo) GetLatestBySessionID(ctx context.Context, sessionID uuid.UUID) (*repositories.SessionEvent, error) {
	query := `
		SELECT id, session_id, username, event_type, session_type,
			threshold_n, threshold_t, party_id, party_index,
			message_hash, public_key, signature, error_message,
			metadata, created_at
		FROM session_events
		WHERE session_id = $1
		ORDER BY created_at DESC
		LIMIT 1
	`

	row := r.db.QueryRowContext(ctx, query, sessionID)
	return r.scanEvent(row)
}

// GetByUsernameAndType retrieves events for a user filtered by session type
func (r *SessionEventPostgresRepo) GetByUsernameAndType(ctx context.Context, username string, sessionType repositories.SessionType, limit int) ([]*repositories.SessionEvent, error) {
	query := `
		SELECT id, session_id, username, event_type, session_type,
			threshold_n, threshold_t, party_id, party_index,
			message_hash, public_key, signature, error_message,
			metadata, created_at
		FROM session_events
		WHERE username = $1 AND session_type = $2
		ORDER BY created_at DESC
		LIMIT $3
	`

	rows, err := r.db.QueryContext(ctx, query, username, string(sessionType), limit)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return r.scanEvents(rows)
}

// scanEvent scans a single event from a row
func (r *SessionEventPostgresRepo) scanEvent(row *sql.Row) (*repositories.SessionEvent, error) {
	var event repositories.SessionEvent
	var eventType, sessionType string
	var metadataJSON []byte

	err := row.Scan(
		&event.ID,
		&event.SessionID,
		&event.Username,
		&eventType,
		&sessionType,
		&event.ThresholdN,
		&event.ThresholdT,
		&event.PartyID,
		&event.PartyIndex,
		&event.MessageHash,
		&event.PublicKey,
		&event.Signature,
		&event.ErrorMessage,
		&metadataJSON,
		&event.CreatedAt,
	)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, nil
		}
		return nil, err
	}

	event.EventType = repositories.SessionEventType(eventType)
	event.SessionType = repositories.SessionType(sessionType)

	if len(metadataJSON) > 0 {
		if err := json.Unmarshal(metadataJSON, &event.Metadata); err != nil {
			return nil, err
		}
	}

	return &event, nil
}

// scanEvents scans multiple events from rows
func (r *SessionEventPostgresRepo) scanEvents(rows *sql.Rows) ([]*repositories.SessionEvent, error) {
	var events []*repositories.SessionEvent

	for rows.Next() {
		var event repositories.SessionEvent
		var eventType, sessionType string
		var metadataJSON []byte

		err := rows.Scan(
			&event.ID,
			&event.SessionID,
			&event.Username,
			&eventType,
			&sessionType,
			&event.ThresholdN,
			&event.ThresholdT,
			&event.PartyID,
			&event.PartyIndex,
			&event.MessageHash,
			&event.PublicKey,
			&event.Signature,
			&event.ErrorMessage,
			&metadataJSON,
			&event.CreatedAt,
		)
		if err != nil {
			return nil, err
		}

		event.EventType = repositories.SessionEventType(eventType)
		event.SessionType = repositories.SessionType(sessionType)

		if len(metadataJSON) > 0 {
			if err := json.Unmarshal(metadataJSON, &event.Metadata); err != nil {
				return nil, err
			}
		}

		events = append(events, &event)
	}

	return events, rows.Err()
}
