package rbac

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

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

// Store provides database operations for RBAC entities
type Store struct {
	pool *pgxpool.Pool
}

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

// ============================================================================
// Tenant Operations
// ============================================================================

// CreateTenant creates a new tenant
func (s *Store) CreateTenant(ctx context.Context, tenant *Tenant) error {
	tenant.ID = uuid.New()
	tenant.CreatedAt = time.Now().UTC()
	tenant.UpdatedAt = tenant.CreatedAt

	if tenant.Status == "" {
		tenant.Status = TenantStatusActive
	}
	if tenant.Settings == nil {
		tenant.Settings = make(map[string]any)
	}

	settingsJSON, err := json.Marshal(tenant.Settings)
	if err != nil {
		return fmt.Errorf("failed to marshal settings: %w", err)
	}

	_, err = s.pool.Exec(ctx, `
		INSERT INTO compliance_tenants (id, name, slug, settings, max_users, llm_quota_monthly, status, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
	`, tenant.ID, tenant.Name, tenant.Slug, settingsJSON, tenant.MaxUsers, tenant.LLMQuotaMonthly, tenant.Status, tenant.CreatedAt, tenant.UpdatedAt)

	return err
}

// GetTenant retrieves a tenant by ID
func (s *Store) GetTenant(ctx context.Context, id uuid.UUID) (*Tenant, error) {
	var tenant Tenant
	var settingsJSON []byte

	err := s.pool.QueryRow(ctx, `
		SELECT id, name, slug, settings, max_users, llm_quota_monthly, status, created_at, updated_at
		FROM compliance_tenants
		WHERE id = $1
	`, id).Scan(
		&tenant.ID, &tenant.Name, &tenant.Slug, &settingsJSON,
		&tenant.MaxUsers, &tenant.LLMQuotaMonthly, &tenant.Status,
		&tenant.CreatedAt, &tenant.UpdatedAt,
	)

	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(settingsJSON, &tenant.Settings); err != nil {
		tenant.Settings = make(map[string]any)
	}

	return &tenant, nil
}

// GetTenantBySlug retrieves a tenant by slug
func (s *Store) GetTenantBySlug(ctx context.Context, slug string) (*Tenant, error) {
	var tenant Tenant
	var settingsJSON []byte

	err := s.pool.QueryRow(ctx, `
		SELECT id, name, slug, settings, max_users, llm_quota_monthly, status, created_at, updated_at
		FROM compliance_tenants
		WHERE slug = $1
	`, slug).Scan(
		&tenant.ID, &tenant.Name, &tenant.Slug, &settingsJSON,
		&tenant.MaxUsers, &tenant.LLMQuotaMonthly, &tenant.Status,
		&tenant.CreatedAt, &tenant.UpdatedAt,
	)

	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(settingsJSON, &tenant.Settings); err != nil {
		tenant.Settings = make(map[string]any)
	}

	return &tenant, nil
}

// ListTenants lists all tenants
func (s *Store) ListTenants(ctx context.Context) ([]*Tenant, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT id, name, slug, settings, max_users, llm_quota_monthly, status, created_at, updated_at
		FROM compliance_tenants
		ORDER BY name
	`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var tenants []*Tenant
	for rows.Next() {
		var tenant Tenant
		var settingsJSON []byte

		err := rows.Scan(
			&tenant.ID, &tenant.Name, &tenant.Slug, &settingsJSON,
			&tenant.MaxUsers, &tenant.LLMQuotaMonthly, &tenant.Status,
			&tenant.CreatedAt, &tenant.UpdatedAt,
		)
		if err != nil {
			continue
		}

		if err := json.Unmarshal(settingsJSON, &tenant.Settings); err != nil {
			tenant.Settings = make(map[string]any)
		}

		tenants = append(tenants, &tenant)
	}

	return tenants, nil
}

// UpdateTenant updates a tenant
func (s *Store) UpdateTenant(ctx context.Context, tenant *Tenant) error {
	tenant.UpdatedAt = time.Now().UTC()

	settingsJSON, err := json.Marshal(tenant.Settings)
	if err != nil {
		return fmt.Errorf("failed to marshal settings: %w", err)
	}

	_, err = s.pool.Exec(ctx, `
		UPDATE compliance_tenants
		SET name = $2, slug = $3, settings = $4, max_users = $5, llm_quota_monthly = $6, status = $7, updated_at = $8
		WHERE id = $1
	`, tenant.ID, tenant.Name, tenant.Slug, settingsJSON, tenant.MaxUsers, tenant.LLMQuotaMonthly, tenant.Status, tenant.UpdatedAt)

	return err
}

// ============================================================================
// Namespace Operations
// ============================================================================

// CreateNamespace creates a new namespace
func (s *Store) CreateNamespace(ctx context.Context, ns *Namespace) error {
	ns.ID = uuid.New()
	ns.CreatedAt = time.Now().UTC()
	ns.UpdatedAt = ns.CreatedAt

	if ns.IsolationLevel == "" {
		ns.IsolationLevel = IsolationStrict
	}
	if ns.DataClassification == "" {
		ns.DataClassification = ClassificationInternal
	}
	if ns.Metadata == nil {
		ns.Metadata = make(map[string]any)
	}

	metadataJSON, err := json.Marshal(ns.Metadata)
	if err != nil {
		return fmt.Errorf("failed to marshal metadata: %w", err)
	}

	_, err = s.pool.Exec(ctx, `
		INSERT INTO compliance_namespaces (id, tenant_id, name, slug, parent_namespace_id, isolation_level, data_classification, metadata, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
	`, ns.ID, ns.TenantID, ns.Name, ns.Slug, ns.ParentNamespaceID, ns.IsolationLevel, ns.DataClassification, metadataJSON, ns.CreatedAt, ns.UpdatedAt)

	return err
}

// GetNamespace retrieves a namespace by ID
func (s *Store) GetNamespace(ctx context.Context, id uuid.UUID) (*Namespace, error) {
	var ns Namespace
	var metadataJSON []byte

	err := s.pool.QueryRow(ctx, `
		SELECT id, tenant_id, name, slug, parent_namespace_id, isolation_level, data_classification, metadata, created_at, updated_at
		FROM compliance_namespaces
		WHERE id = $1
	`, id).Scan(
		&ns.ID, &ns.TenantID, &ns.Name, &ns.Slug, &ns.ParentNamespaceID,
		&ns.IsolationLevel, &ns.DataClassification, &metadataJSON,
		&ns.CreatedAt, &ns.UpdatedAt,
	)

	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(metadataJSON, &ns.Metadata); err != nil {
		ns.Metadata = make(map[string]any)
	}

	return &ns, nil
}

// GetNamespaceBySlug retrieves a namespace by tenant and slug
func (s *Store) GetNamespaceBySlug(ctx context.Context, tenantID uuid.UUID, slug string) (*Namespace, error) {
	var ns Namespace
	var metadataJSON []byte

	err := s.pool.QueryRow(ctx, `
		SELECT id, tenant_id, name, slug, parent_namespace_id, isolation_level, data_classification, metadata, created_at, updated_at
		FROM compliance_namespaces
		WHERE tenant_id = $1 AND slug = $2
	`, tenantID, slug).Scan(
		&ns.ID, &ns.TenantID, &ns.Name, &ns.Slug, &ns.ParentNamespaceID,
		&ns.IsolationLevel, &ns.DataClassification, &metadataJSON,
		&ns.CreatedAt, &ns.UpdatedAt,
	)

	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(metadataJSON, &ns.Metadata); err != nil {
		ns.Metadata = make(map[string]any)
	}

	return &ns, nil
}

// ListNamespaces lists namespaces for a tenant
func (s *Store) ListNamespaces(ctx context.Context, tenantID uuid.UUID) ([]*Namespace, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT id, tenant_id, name, slug, parent_namespace_id, isolation_level, data_classification, metadata, created_at, updated_at
		FROM compliance_namespaces
		WHERE tenant_id = $1
		ORDER BY name
	`, tenantID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var namespaces []*Namespace
	for rows.Next() {
		var ns Namespace
		var metadataJSON []byte

		err := rows.Scan(
			&ns.ID, &ns.TenantID, &ns.Name, &ns.Slug, &ns.ParentNamespaceID,
			&ns.IsolationLevel, &ns.DataClassification, &metadataJSON,
			&ns.CreatedAt, &ns.UpdatedAt,
		)
		if err != nil {
			continue
		}

		if err := json.Unmarshal(metadataJSON, &ns.Metadata); err != nil {
			ns.Metadata = make(map[string]any)
		}

		namespaces = append(namespaces, &ns)
	}

	return namespaces, nil
}
