package policy

import (
	"context"
	"fmt"
	"time"

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

// Store provides database operations for the policy system.
type Store struct {
	pool *pgxpool.Pool
}

// NewStore creates a new Store instance.
func NewStore(pool *pgxpool.Pool) *Store {
	return &Store{pool: pool}
}

// =============================================================================
// SOURCE POLICIES
// =============================================================================

// CreatePolicy creates a new source policy.
func (s *Store) CreatePolicy(ctx context.Context, req *CreateSourcePolicyRequest) (*SourcePolicy, error) {
	policy := &SourcePolicy{
		ID:         uuid.New(),
		Version:    1,
		Name:       req.Name,
		Description: req.Description,
		Bundesland: req.Bundesland,
		IsActive:   true,
		CreatedAt:  time.Now(),
		UpdatedAt:  time.Now(),
	}

	query := `
		INSERT INTO source_policies (id, version, name, description, bundesland, is_active, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
		RETURNING id, version, name, description, bundesland, is_active, created_at, updated_at`

	err := s.pool.QueryRow(ctx, query,
		policy.ID, policy.Version, policy.Name, policy.Description,
		policy.Bundesland, policy.IsActive, policy.CreatedAt, policy.UpdatedAt,
	).Scan(
		&policy.ID, &policy.Version, &policy.Name, &policy.Description,
		&policy.Bundesland, &policy.IsActive, &policy.CreatedAt, &policy.UpdatedAt,
	)

	if err != nil {
		return nil, fmt.Errorf("failed to create policy: %w", err)
	}

	return policy, nil
}

// GetPolicy retrieves a policy by ID.
func (s *Store) GetPolicy(ctx context.Context, id uuid.UUID) (*SourcePolicy, error) {
	query := `
		SELECT id, version, name, description, bundesland, is_active,
		       created_at, updated_at, approved_by, approved_at
		FROM source_policies
		WHERE id = $1`

	policy := &SourcePolicy{}
	err := s.pool.QueryRow(ctx, query, id).Scan(
		&policy.ID, &policy.Version, &policy.Name, &policy.Description,
		&policy.Bundesland, &policy.IsActive, &policy.CreatedAt, &policy.UpdatedAt,
		&policy.ApprovedBy, &policy.ApprovedAt,
	)

	if err == pgx.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("failed to get policy: %w", err)
	}

	return policy, nil
}

// ListPolicies retrieves policies with optional filters.
func (s *Store) ListPolicies(ctx context.Context, filter *PolicyListFilter) ([]SourcePolicy, int, error) {
	baseQuery := `FROM source_policies WHERE 1=1`
	args := []interface{}{}
	argCount := 0

	if filter.Bundesland != nil {
		argCount++
		baseQuery += fmt.Sprintf(" AND bundesland = $%d", argCount)
		args = append(args, *filter.Bundesland)
	}

	if filter.IsActive != nil {
		argCount++
		baseQuery += fmt.Sprintf(" AND is_active = $%d", argCount)
		args = append(args, *filter.IsActive)
	}

	// Count query
	var total int
	countQuery := "SELECT COUNT(*) " + baseQuery
	err := s.pool.QueryRow(ctx, countQuery, args...).Scan(&total)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to count policies: %w", err)
	}

	// Data query with pagination
	dataQuery := `SELECT id, version, name, description, bundesland, is_active,
	              created_at, updated_at, approved_by, approved_at ` + baseQuery +
		` ORDER BY created_at DESC`

	if filter.Limit > 0 {
		argCount++
		dataQuery += fmt.Sprintf(" LIMIT $%d", argCount)
		args = append(args, filter.Limit)
	}
	if filter.Offset > 0 {
		argCount++
		dataQuery += fmt.Sprintf(" OFFSET $%d", argCount)
		args = append(args, filter.Offset)
	}

	rows, err := s.pool.Query(ctx, dataQuery, args...)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to list policies: %w", err)
	}
	defer rows.Close()

	policies := []SourcePolicy{}
	for rows.Next() {
		var p SourcePolicy
		err := rows.Scan(
			&p.ID, &p.Version, &p.Name, &p.Description,
			&p.Bundesland, &p.IsActive, &p.CreatedAt, &p.UpdatedAt,
			&p.ApprovedBy, &p.ApprovedAt,
		)
		if err != nil {
			return nil, 0, fmt.Errorf("failed to scan policy: %w", err)
		}
		policies = append(policies, p)
	}

	return policies, total, nil
}

// UpdatePolicy updates an existing policy.
func (s *Store) UpdatePolicy(ctx context.Context, id uuid.UUID, req *UpdateSourcePolicyRequest) (*SourcePolicy, error) {
	policy, err := s.GetPolicy(ctx, id)
	if err != nil {
		return nil, err
	}
	if policy == nil {
		return nil, fmt.Errorf("policy not found")
	}

	if req.Name != nil {
		policy.Name = *req.Name
	}
	if req.Description != nil {
		policy.Description = req.Description
	}
	if req.Bundesland != nil {
		policy.Bundesland = req.Bundesland
	}
	if req.IsActive != nil {
		policy.IsActive = *req.IsActive
	}
	policy.Version++
	policy.UpdatedAt = time.Now()

	query := `
		UPDATE source_policies
		SET version = $2, name = $3, description = $4, bundesland = $5,
		    is_active = $6, updated_at = $7
		WHERE id = $1
		RETURNING id, version, name, description, bundesland, is_active, created_at, updated_at`

	err = s.pool.QueryRow(ctx, query,
		id, policy.Version, policy.Name, policy.Description,
		policy.Bundesland, policy.IsActive, policy.UpdatedAt,
	).Scan(
		&policy.ID, &policy.Version, &policy.Name, &policy.Description,
		&policy.Bundesland, &policy.IsActive, &policy.CreatedAt, &policy.UpdatedAt,
	)

	if err != nil {
		return nil, fmt.Errorf("failed to update policy: %w", err)
	}

	return policy, nil
}

// DeletePolicy deletes a policy by ID (soft delete via is_active = false).
func (s *Store) DeletePolicy(ctx context.Context, id uuid.UUID) error {
	query := `UPDATE source_policies SET is_active = false, updated_at = $2 WHERE id = $1`
	_, err := s.pool.Exec(ctx, query, id, time.Now())
	if err != nil {
		return fmt.Errorf("failed to delete policy: %w", err)
	}
	return nil
}

// =============================================================================
// PII RULES
// =============================================================================

// CreatePIIRule creates a new PII rule.
func (s *Store) CreatePIIRule(ctx context.Context, req *CreatePIIRuleRequest) (*PIIRule, error) {
	severity := PIISeverityBlock
	if req.Severity != "" {
		severity = req.Severity
	}

	rule := &PIIRule{
		ID:          uuid.New(),
		Name:        req.Name,
		Description: req.Description,
		RuleType:    req.RuleType,
		Pattern:     req.Pattern,
		Severity:    severity,
		IsActive:    true,
		CreatedAt:   time.Now(),
		UpdatedAt:   time.Now(),
	}

	query := `
		INSERT INTO pii_rules (id, name, description, rule_type, pattern, severity, is_active, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
		RETURNING id`

	err := s.pool.QueryRow(ctx, query,
		rule.ID, rule.Name, rule.Description, rule.RuleType, rule.Pattern,
		rule.Severity, rule.IsActive, rule.CreatedAt, rule.UpdatedAt,
	).Scan(&rule.ID)

	if err != nil {
		return nil, fmt.Errorf("failed to create PII rule: %w", err)
	}

	return rule, nil
}

// GetPIIRule retrieves a PII rule by ID.
func (s *Store) GetPIIRule(ctx context.Context, id uuid.UUID) (*PIIRule, error) {
	query := `
		SELECT id, name, description, rule_type, pattern, severity, is_active, created_at, updated_at
		FROM pii_rules
		WHERE id = $1`

	rule := &PIIRule{}
	err := s.pool.QueryRow(ctx, query, id).Scan(
		&rule.ID, &rule.Name, &rule.Description, &rule.RuleType, &rule.Pattern,
		&rule.Severity, &rule.IsActive, &rule.CreatedAt, &rule.UpdatedAt,
	)

	if err == pgx.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("failed to get PII rule: %w", err)
	}

	return rule, nil
}

// ListPIIRules retrieves all PII rules.
func (s *Store) ListPIIRules(ctx context.Context, activeOnly bool) ([]PIIRule, error) {
	query := `
		SELECT id, name, description, rule_type, pattern, severity, is_active, created_at, updated_at
		FROM pii_rules`

	if activeOnly {
		query += ` WHERE is_active = true`
	}
	query += ` ORDER BY severity DESC, name`

	rows, err := s.pool.Query(ctx, query)
	if err != nil {
		return nil, fmt.Errorf("failed to list PII rules: %w", err)
	}
	defer rows.Close()

	rules := []PIIRule{}
	for rows.Next() {
		var r PIIRule
		err := rows.Scan(
			&r.ID, &r.Name, &r.Description, &r.RuleType, &r.Pattern,
			&r.Severity, &r.IsActive, &r.CreatedAt, &r.UpdatedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("failed to scan PII rule: %w", err)
		}
		rules = append(rules, r)
	}

	return rules, nil
}

// UpdatePIIRule updates an existing PII rule.
func (s *Store) UpdatePIIRule(ctx context.Context, id uuid.UUID, req *UpdatePIIRuleRequest) (*PIIRule, error) {
	rule, err := s.GetPIIRule(ctx, id)
	if err != nil {
		return nil, err
	}
	if rule == nil {
		return nil, fmt.Errorf("PII rule not found")
	}

	if req.Name != nil {
		rule.Name = *req.Name
	}
	if req.Description != nil {
		rule.Description = req.Description
	}
	if req.RuleType != nil {
		rule.RuleType = *req.RuleType
	}
	if req.Pattern != nil {
		rule.Pattern = *req.Pattern
	}
	if req.Severity != nil {
		rule.Severity = *req.Severity
	}
	if req.IsActive != nil {
		rule.IsActive = *req.IsActive
	}
	rule.UpdatedAt = time.Now()

	query := `
		UPDATE pii_rules
		SET name = $2, description = $3, rule_type = $4, pattern = $5,
		    severity = $6, is_active = $7, updated_at = $8
		WHERE id = $1`

	_, err = s.pool.Exec(ctx, query,
		id, rule.Name, rule.Description, rule.RuleType, rule.Pattern,
		rule.Severity, rule.IsActive, rule.UpdatedAt,
	)

	if err != nil {
		return nil, fmt.Errorf("failed to update PII rule: %w", err)
	}

	return rule, nil
}

// DeletePIIRule deletes a PII rule by ID.
func (s *Store) DeletePIIRule(ctx context.Context, id uuid.UUID) error {
	query := `DELETE FROM pii_rules WHERE id = $1`
	_, err := s.pool.Exec(ctx, query, id)
	if err != nil {
		return fmt.Errorf("failed to delete PII rule: %w", err)
	}
	return nil
}
