package policy

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

	"github.com/google/uuid"
)

// =============================================================================
// AUDIT LOG
// =============================================================================

// CreateAuditLog creates a new audit log entry.
func (s *Store) CreateAuditLog(ctx context.Context, entry *PolicyAuditLog) error {
	entry.ID = uuid.New()
	entry.CreatedAt = time.Now()

	query := `
		INSERT INTO policy_audit_log (id, action, entity_type, entity_id, old_value, new_value,
		                              user_id, user_email, ip_address, user_agent, created_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`

	_, err := s.pool.Exec(ctx, query,
		entry.ID, entry.Action, entry.EntityType, entry.EntityID,
		entry.OldValue, entry.NewValue, entry.UserID, entry.UserEmail,
		entry.IPAddress, entry.UserAgent, entry.CreatedAt,
	)

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

	return nil
}

// ListAuditLogs retrieves audit logs with filters.
func (s *Store) ListAuditLogs(ctx context.Context, filter *AuditLogFilter) ([]PolicyAuditLog, int, error) {
	baseQuery := `FROM policy_audit_log WHERE 1=1`
	args := []interface{}{}
	argCount := 0

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

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

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

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

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

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

	// 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 audit logs: %w", err)
	}

	// Data query
	dataQuery := `SELECT id, action, entity_type, entity_id, old_value, new_value,
	              user_id, user_email, ip_address, user_agent, created_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 audit logs: %w", err)
	}
	defer rows.Close()

	logs := []PolicyAuditLog{}
	for rows.Next() {
		var l PolicyAuditLog
		err := rows.Scan(
			&l.ID, &l.Action, &l.EntityType, &l.EntityID, &l.OldValue, &l.NewValue,
			&l.UserID, &l.UserEmail, &l.IPAddress, &l.UserAgent, &l.CreatedAt,
		)
		if err != nil {
			return nil, 0, fmt.Errorf("failed to scan audit log: %w", err)
		}
		logs = append(logs, l)
	}

	return logs, total, nil
}

// =============================================================================
// BLOCKED CONTENT LOG
// =============================================================================

// CreateBlockedContentLog creates a new blocked content log entry.
func (s *Store) CreateBlockedContentLog(ctx context.Context, entry *BlockedContentLog) error {
	entry.ID = uuid.New()
	entry.CreatedAt = time.Now()

	query := `
		INSERT INTO blocked_content_log (id, url, domain, block_reason, matched_rule_id, details, created_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7)`

	_, err := s.pool.Exec(ctx, query,
		entry.ID, entry.URL, entry.Domain, entry.BlockReason,
		entry.MatchedRuleID, entry.Details, entry.CreatedAt,
	)

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

	return nil
}

// ListBlockedContent retrieves blocked content logs with filters.
func (s *Store) ListBlockedContent(ctx context.Context, filter *BlockedContentFilter) ([]BlockedContentLog, int, error) {
	baseQuery := `FROM blocked_content_log WHERE 1=1`
	args := []interface{}{}
	argCount := 0

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

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

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

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

	// 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 blocked content: %w", err)
	}

	// Data query
	dataQuery := `SELECT id, url, domain, block_reason, matched_rule_id, details, created_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 blocked content: %w", err)
	}
	defer rows.Close()

	logs := []BlockedContentLog{}
	for rows.Next() {
		var l BlockedContentLog
		err := rows.Scan(
			&l.ID, &l.URL, &l.Domain, &l.BlockReason,
			&l.MatchedRuleID, &l.Details, &l.CreatedAt,
		)
		if err != nil {
			return nil, 0, fmt.Errorf("failed to scan blocked content: %w", err)
		}
		logs = append(logs, l)
	}

	return logs, total, nil
}

// =============================================================================
// STATISTICS
// =============================================================================

// GetStats retrieves aggregated statistics for the policy system.
func (s *Store) GetStats(ctx context.Context) (*PolicyStats, error) {
	stats := &PolicyStats{
		SourcesByLicense: make(map[string]int),
		BlocksByReason:   make(map[string]int),
	}

	// Active policies
	err := s.pool.QueryRow(ctx, `SELECT COUNT(*) FROM source_policies WHERE is_active = true`).Scan(&stats.ActivePolicies)
	if err != nil {
		return nil, fmt.Errorf("failed to count active policies: %w", err)
	}

	// Total sources
	err = s.pool.QueryRow(ctx, `SELECT COUNT(*) FROM allowed_sources`).Scan(&stats.TotalSources)
	if err != nil {
		return nil, fmt.Errorf("failed to count total sources: %w", err)
	}

	// Active sources
	err = s.pool.QueryRow(ctx, `SELECT COUNT(*) FROM allowed_sources WHERE is_active = true`).Scan(&stats.ActiveSources)
	if err != nil {
		return nil, fmt.Errorf("failed to count active sources: %w", err)
	}

	// Blocked today
	err = s.pool.QueryRow(ctx, `SELECT COUNT(*) FROM blocked_content_log WHERE created_at >= CURRENT_DATE`).Scan(&stats.BlockedToday)
	if err != nil {
		return nil, fmt.Errorf("failed to count blocked today: %w", err)
	}

	// Blocked total
	err = s.pool.QueryRow(ctx, `SELECT COUNT(*) FROM blocked_content_log`).Scan(&stats.BlockedTotal)
	if err != nil {
		return nil, fmt.Errorf("failed to count blocked total: %w", err)
	}

	// Active PII rules
	err = s.pool.QueryRow(ctx, `SELECT COUNT(*) FROM pii_rules WHERE is_active = true`).Scan(&stats.PIIRulesActive)
	if err != nil {
		return nil, fmt.Errorf("failed to count active PII rules: %w", err)
	}

	// Sources by license
	rows, err := s.pool.Query(ctx, `SELECT license, COUNT(*) FROM allowed_sources GROUP BY license`)
	if err != nil {
		return nil, fmt.Errorf("failed to count sources by license: %w", err)
	}
	defer rows.Close()
	for rows.Next() {
		var license string
		var count int
		if err := rows.Scan(&license, &count); err != nil {
			return nil, err
		}
		stats.SourcesByLicense[license] = count
	}

	// Blocks by reason
	rows, err = s.pool.Query(ctx, `SELECT block_reason, COUNT(*) FROM blocked_content_log GROUP BY block_reason`)
	if err != nil {
		return nil, fmt.Errorf("failed to count blocks by reason: %w", err)
	}
	defer rows.Close()
	for rows.Next() {
		var reason string
		var count int
		if err := rows.Scan(&reason, &count); err != nil {
			return nil, err
		}
		stats.BlocksByReason[reason] = count
	}

	// Compliance score (simplified: active sources / total sources)
	if stats.TotalSources > 0 {
		stats.ComplianceScore = float64(stats.ActiveSources) / float64(stats.TotalSources) * 100
	}

	return stats, nil
}

// =============================================================================
// YAML LOADER
// =============================================================================

// LoadFromYAML loads initial policy data from YAML configuration.
func (s *Store) LoadFromYAML(ctx context.Context, config *BundeslaenderConfig) error {
	// Load federal policy
	if config.Federal.Name != "" {
		err := s.loadPolicy(ctx, nil, &config.Federal, &config.DefaultOperations)
		if err != nil {
			return fmt.Errorf("failed to load federal policy: %w", err)
		}
	}

	// Load Bundesland policies
	for code, policyConfig := range config.Bundeslaender {
		if code == "federal" || code == "default_operations" || code == "pii_rules" {
			continue
		}
		bl := Bundesland(code)
		err := s.loadPolicy(ctx, &bl, &policyConfig, &config.DefaultOperations)
		if err != nil {
			return fmt.Errorf("failed to load policy for %s: %w", code, err)
		}
	}

	// Load PII rules
	for _, ruleConfig := range config.PIIRules {
		err := s.loadPIIRule(ctx, &ruleConfig)
		if err != nil {
			return fmt.Errorf("failed to load PII rule %s: %w", ruleConfig.Name, err)
		}
	}

	return nil
}

func (s *Store) loadPolicy(ctx context.Context, bundesland *Bundesland, config *PolicyConfig, ops *OperationsConfig) error {
	// Create policy
	policy, err := s.CreatePolicy(ctx, &CreateSourcePolicyRequest{
		Name:       config.Name,
		Bundesland: bundesland,
	})
	if err != nil {
		return err
	}

	// Create sources
	for _, srcConfig := range config.Sources {
		trustBoost := 0.5
		if srcConfig.TrustBoost > 0 {
			trustBoost = srcConfig.TrustBoost
		}

		var legalBasis, citation *string
		if srcConfig.LegalBasis != "" {
			legalBasis = &srcConfig.LegalBasis
		}
		if srcConfig.CitationTemplate != "" {
			citation = &srcConfig.CitationTemplate
		}

		_, err := s.CreateSource(ctx, &CreateAllowedSourceRequest{
			PolicyID:         policy.ID,
			Domain:           srcConfig.Domain,
			Name:             srcConfig.Name,
			License:          License(srcConfig.License),
			LegalBasis:       legalBasis,
			CitationTemplate: citation,
			TrustBoost:       &trustBoost,
		})
		if err != nil {
			return fmt.Errorf("failed to create source %s: %w", srcConfig.Domain, err)
		}
	}

	return nil
}

func (s *Store) loadPIIRule(ctx context.Context, config *PIIRuleConfig) error {
	severity := PIISeverityBlock
	if config.Severity != "" {
		severity = PIISeverity(config.Severity)
	}

	_, err := s.CreatePIIRule(ctx, &CreatePIIRuleRequest{
		Name:     config.Name,
		RuleType: PIIRuleType(config.Type),
		Pattern:  config.Pattern,
		Severity: severity,
	})

	return err
}

// ToJSON converts an entity to JSON for audit logging.
func ToJSON(v interface{}) json.RawMessage {
	data, _ := json.Marshal(v)
	return data
}
