package db

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"
)

// Pool wraps a pgxpool.Pool with SDK-specific methods
type Pool struct {
	*pgxpool.Pool
}

// SDKState represents the state stored in the database
type SDKState struct {
	ID        string          `json:"id"`
	TenantID  string          `json:"tenant_id"`
	UserID    string          `json:"user_id,omitempty"`
	State     json.RawMessage `json:"state"`
	Version   int             `json:"version"`
	CreatedAt time.Time       `json:"created_at"`
	UpdatedAt time.Time       `json:"updated_at"`
}

// NewPostgresPool creates a new database connection pool
func NewPostgresPool(connectionString string) (*Pool, error) {
	config, err := pgxpool.ParseConfig(connectionString)
	if err != nil {
		return nil, fmt.Errorf("failed to parse connection string: %w", err)
	}

	config.MaxConns = 10
	config.MinConns = 2
	config.MaxConnLifetime = 1 * time.Hour
	config.MaxConnIdleTime = 30 * time.Minute

	pool, err := pgxpool.NewWithConfig(context.Background(), config)
	if err != nil {
		return nil, fmt.Errorf("failed to create connection pool: %w", err)
	}

	// Test connection
	if err := pool.Ping(context.Background()); err != nil {
		return nil, fmt.Errorf("failed to ping database: %w", err)
	}

	return &Pool{Pool: pool}, nil
}

// GetState retrieves state for a tenant
func (p *Pool) GetState(ctx context.Context, tenantID string) (*SDKState, error) {
	query := `
		SELECT id, tenant_id, user_id, state, version, created_at, updated_at
		FROM sdk_states
		WHERE tenant_id = $1
	`

	var state SDKState
	err := p.QueryRow(ctx, query, tenantID).Scan(
		&state.ID,
		&state.TenantID,
		&state.UserID,
		&state.State,
		&state.Version,
		&state.CreatedAt,
		&state.UpdatedAt,
	)
	if err != nil {
		return nil, err
	}

	return &state, nil
}

// SaveState saves or updates state for a tenant with optimistic locking
func (p *Pool) SaveState(ctx context.Context, tenantID string, userID string, state json.RawMessage, expectedVersion *int) (*SDKState, error) {
	query := `
		INSERT INTO sdk_states (tenant_id, user_id, state, version)
		VALUES ($1, $2, $3, 1)
		ON CONFLICT (tenant_id) DO UPDATE SET
			state = $3,
			user_id = COALESCE($2, sdk_states.user_id),
			version = sdk_states.version + 1,
			updated_at = NOW()
		WHERE ($4::int IS NULL OR sdk_states.version = $4)
		RETURNING id, tenant_id, user_id, state, version, created_at, updated_at
	`

	var result SDKState
	err := p.QueryRow(ctx, query, tenantID, userID, state, expectedVersion).Scan(
		&result.ID,
		&result.TenantID,
		&result.UserID,
		&result.State,
		&result.Version,
		&result.CreatedAt,
		&result.UpdatedAt,
	)
	if err != nil {
		return nil, err
	}

	return &result, nil
}

// DeleteState deletes state for a tenant
func (p *Pool) DeleteState(ctx context.Context, tenantID string) error {
	query := `DELETE FROM sdk_states WHERE tenant_id = $1`
	_, err := p.Exec(ctx, query, tenantID)
	return err
}

// InMemoryStore provides an in-memory fallback when database is not available
type InMemoryStore struct {
	states map[string]*SDKState
}

// NewInMemoryStore creates a new in-memory store
func NewInMemoryStore() *InMemoryStore {
	return &InMemoryStore{
		states: make(map[string]*SDKState),
	}
}

// GetState retrieves state from memory
func (s *InMemoryStore) GetState(tenantID string) (*SDKState, error) {
	state, ok := s.states[tenantID]
	if !ok {
		return nil, fmt.Errorf("state not found")
	}
	return state, nil
}

// SaveState saves state to memory
func (s *InMemoryStore) SaveState(tenantID string, userID string, state json.RawMessage, expectedVersion *int) (*SDKState, error) {
	existing, exists := s.states[tenantID]

	// Optimistic locking check
	if expectedVersion != nil && exists && existing.Version != *expectedVersion {
		return nil, fmt.Errorf("version conflict")
	}

	now := time.Now()
	version := 1
	createdAt := now

	if exists {
		version = existing.Version + 1
		createdAt = existing.CreatedAt
	}

	newState := &SDKState{
		ID:        fmt.Sprintf("%s-%d", tenantID, time.Now().UnixNano()),
		TenantID:  tenantID,
		UserID:    userID,
		State:     state,
		Version:   version,
		CreatedAt: createdAt,
		UpdatedAt: now,
	}

	s.states[tenantID] = newState
	return newState, nil
}

// DeleteState deletes state from memory
func (s *InMemoryStore) DeleteState(tenantID string) error {
	delete(s.states, tenantID)
	return nil
}
