package sync

import (
	"context"
	"database/sql"
	"fmt"
	"time"
)

// Repository provides database access for sync operations.
type Repository struct {
	db *sql.DB
}

func NewRepository(db *sql.DB) *Repository {
	return &Repository{db: db}
}

// APIKeyRecord represents a stored API key.
type APIKeyRecord struct {
	ID         string    `json:"id"`
	UserID     string    `json:"userId"`
	Name       string    `json:"name"`
	KeyHash    string    `json:"keyHash"`
	Scopes     string    `json:"scopes"`
	CreatedAt  time.Time `json:"createdAt"`
	ExpiresAt  *time.Time `json:"expiresAt,omitempty"`
	LastUsedAt *time.Time `json:"lastUsedAt,omitempty"`
}

// CreateSnapshot persists a new snapshot and returns it with an ID.
func (r *Repository) CreateSnapshot(ctx context.Context, deviceID, userID string, files []FileEntry) (*Snapshot, error) {
	id := generateID("snap", deviceID)
	now := time.Now().UTC()

	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return nil, fmt.Errorf("begin transaction: %w", err)
	}
	defer tx.Rollback()

	if _, err := tx.ExecContext(ctx, `
		INSERT INTO sync_snapshots (id, device_id, user_id, created_at)
		VALUES (?, ?, ?, ?)
	`, id, deviceID, userID, now.Format(time.RFC3339)); err != nil {
		return nil, fmt.Errorf("insert snapshot: %w", err)
	}

	for _, f := range files {
		if _, err := tx.ExecContext(ctx, `
			INSERT INTO sync_files (snapshot_id, path, hash, size_bytes, modified_at)
			VALUES (?, ?, ?, ?, ?)
		`, id, f.Path, f.Hash, f.Size, f.Modified.Format(time.RFC3339)); err != nil {
			return nil, fmt.Errorf("insert sync file %s: %w", f.Path, err)
		}
	}

	if err := tx.Commit(); err != nil {
		return nil, fmt.Errorf("commit snapshot: %w", err)
	}

	return &Snapshot{
		ID:        id,
		DeviceID:  deviceID,
		CreatedAt: now,
		Files:     files,
	}, nil
}

// GetSnapshot retrieves a snapshot by ID.
func (r *Repository) GetSnapshot(ctx context.Context, snapshotID string) (*Snapshot, error) {
	var snap Snapshot
	var created string
	var userID sql.NullString

	err := r.db.QueryRowContext(ctx, `
		SELECT id, device_id, user_id, created_at
		FROM sync_snapshots
		WHERE id = ?
	`, snapshotID).Scan(&snap.ID, &snap.DeviceID, &userID, &created)
	if err != nil {
		return nil, err
	}

	snap.CreatedAt, err = time.Parse(time.RFC3339, created)
	if err != nil {
		return nil, fmt.Errorf("parse created_at: %w", err)
	}
	if userID.Valid {
		snap.UserID = userID.String
	}

	rows, err := r.db.QueryContext(ctx, `
		SELECT path, hash, size_bytes, modified_at
		FROM sync_files
		WHERE snapshot_id = ?
		ORDER BY path ASC
	`, snapshotID)
	if err != nil {
		return nil, fmt.Errorf("query sync files: %w", err)
	}
	defer rows.Close()

	for rows.Next() {
		var f FileEntry
		var modified string
		if err := rows.Scan(&f.Path, &f.Hash, &f.Size, &modified); err != nil {
			return nil, fmt.Errorf("scan sync file: %w", err)
		}
		f.Modified, err = time.Parse(time.RFC3339, modified)
		if err != nil {
			return nil, fmt.Errorf("parse modified_at: %w", err)
		}
		snap.Files = append(snap.Files, f)
	}

	return &snap, rows.Err()
}

// ValidateAPIKey checks if an API key hash is valid and returns the associated user.
func (r *Repository) ValidateAPIKey(ctx context.Context, keyHash string) (*APIKeyRecord, error) {
	var record APIKeyRecord
	var created, expires, lastUsed string

	err := r.db.QueryRowContext(ctx, `
		SELECT id, user_id, name, key_hash, scopes, created_at, expires_at, last_used_at
		FROM api_keys
		WHERE key_hash = ?
	`, keyHash).Scan(
		&record.ID, &record.UserID, &record.Name, &record.KeyHash,
		&record.Scopes, &created, &expires, &lastUsed,
	)
	if err != nil {
		return nil, err
	}

	record.CreatedAt, err = time.Parse(time.RFC3339, created)
	if err != nil {
		return nil, fmt.Errorf("parse created_at: %w", err)
	}
	if expires != "" {
		t, err := time.Parse(time.RFC3339, expires)
		if err != nil {
			return nil, fmt.Errorf("parse expires_at: %w", err)
		}
		if time.Now().UTC().After(t) {
			return nil, fmt.Errorf("api key expired")
		}
		record.ExpiresAt = &t
	}
	if lastUsed != "" {
		t, err := time.Parse(time.RFC3339, lastUsed)
		if err != nil {
			return nil, fmt.Errorf("parse last_used_at: %w", err)
		}
		record.LastUsedAt = &t
	}

	// Update last_used_at
	now := time.Now().UTC().Format(time.RFC3339)
	if _, err := r.db.ExecContext(ctx, `
		UPDATE api_keys SET last_used_at = ? WHERE id = ?
	`, now, record.ID); err != nil {
		return nil, fmt.Errorf("update last_used_at: %w", err)
	}

	return &record, nil
}

// ListLatestFiles returns the most recent file states from the latest snapshot.
func (r *Repository) ListLatestFiles(ctx context.Context, deviceID string) ([]FileEntry, error) {
	rows, err := r.db.QueryContext(ctx, `
		SELECT sf.path, sf.hash, sf.size_bytes, sf.modified_at
		FROM sync_files sf
		JOIN sync_snapshots ss ON ss.id = sf.snapshot_id
		WHERE ss.device_id = ?
		AND ss.created_at = (
			SELECT MAX(created_at) FROM sync_snapshots WHERE device_id = ?
		)
		ORDER BY sf.path ASC
	`, deviceID, deviceID)
	if err != nil {
		return nil, fmt.Errorf("query latest files: %w", err)
	}
	defer rows.Close()

	var files []FileEntry
	for rows.Next() {
		var f FileEntry
		var modified string
		if err := rows.Scan(&f.Path, &f.Hash, &f.Size, &modified); err != nil {
			return nil, fmt.Errorf("scan file: %w", err)
		}
		f.Modified, err = time.Parse(time.RFC3339, modified)
		if err != nil {
			return nil, fmt.Errorf("parse modified: %w", err)
		}
		files = append(files, f)
	}

	return files, rows.Err()
}

func generateID(prefix, suffix string) string {
	return fmt.Sprintf("%s:%s:%d", prefix, suffix, time.Now().UnixNano())
}
