package postgres

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

	"github.com/google/uuid"
	"github.com/ocdp/cluster-service/internal/domain/entity"
	"github.com/ocdp/cluster-service/internal/domain/repository"
)

type WorkspaceRepository struct {
	db *DB
}

func NewWorkspaceRepository(db *DB) repository.WorkspaceRepository {
	return &WorkspaceRepository{db: db}
}

func (r *WorkspaceRepository) Create(ctx context.Context, workspace *entity.Workspace) error {
	if workspace.ID == "" {
		workspace.ID = uuid.New().String()
	}
	query := `
		INSERT INTO workspaces (id, name, status, k8s_namespace, k8s_sa_name, default_cluster_id, quota_cpu, quota_memory, quota_gpu, quota_gpu_memory, created_by, created_at, updated_at)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
		ON CONFLICT (name) DO NOTHING
	`
	result, err := r.db.conn.ExecContext(ctx, query,
		workspace.ID,
		workspace.Name,
		workspace.Status,
		workspace.K8sNamespace,
		workspace.K8sSAName,
		workspace.DefaultClusterID,
		workspace.QuotaCPU,
		workspace.QuotaMemory,
		workspace.QuotaGPU,
		workspace.QuotaGPUMem,
		workspace.CreatedBy,
		workspace.CreatedAt,
		workspace.UpdatedAt,
	)
	if err != nil {
		return fmt.Errorf("failed to create workspace: %w", err)
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get affected rows: %w", err)
	}
	if rows == 0 {
		return entity.ErrWorkspaceExists
	}
	return nil
}

func (r *WorkspaceRepository) GetByID(ctx context.Context, id string) (*entity.Workspace, error) {
	return r.get(ctx, "id = $1", id)
}

func (r *WorkspaceRepository) GetByName(ctx context.Context, name string) (*entity.Workspace, error) {
	return r.get(ctx, "name = $1", name)
}

func (r *WorkspaceRepository) get(ctx context.Context, where string, arg interface{}) (*entity.Workspace, error) {
	query := fmt.Sprintf(`
		SELECT id, name, status, k8s_namespace, k8s_sa_name, default_cluster_id, quota_cpu, quota_memory, quota_gpu, quota_gpu_memory, created_by, created_at, updated_at
		FROM workspaces
		WHERE %s
	`, where)
	workspace := &entity.Workspace{}
	var createdBy, defaultClusterID, quotaCPU, quotaMemory, quotaGPU, quotaGPUMem sql.NullString
	err := r.db.conn.QueryRowContext(ctx, query, arg).Scan(
		&workspace.ID,
		&workspace.Name,
		&workspace.Status,
		&workspace.K8sNamespace,
		&workspace.K8sSAName,
		&defaultClusterID,
		&quotaCPU,
		&quotaMemory,
		&quotaGPU,
		&quotaGPUMem,
		&createdBy,
		&workspace.CreatedAt,
		&workspace.UpdatedAt,
	)
	if err == sql.ErrNoRows {
		return nil, entity.ErrWorkspaceNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("failed to get workspace: %w", err)
	}
	workspace.CreatedBy = createdBy.String
	workspace.DefaultClusterID = defaultClusterID.String
	workspace.QuotaCPU = quotaCPU.String
	workspace.QuotaMemory = quotaMemory.String
	workspace.QuotaGPU = quotaGPU.String
	workspace.QuotaGPUMem = quotaGPUMem.String
	return workspace, nil
}

func (r *WorkspaceRepository) Update(ctx context.Context, workspace *entity.Workspace) error {
	workspace.UpdatedAt = time.Now()
	query := `
		UPDATE workspaces
		SET name = $1, status = $2, k8s_namespace = $3, k8s_sa_name = $4,
		    default_cluster_id = $5,
		    quota_cpu = $6, quota_memory = $7, quota_gpu = $8, quota_gpu_memory = $9,
		    created_by = $10, updated_at = $11
		WHERE id = $12
	`
	result, err := r.db.conn.ExecContext(ctx, query,
		workspace.Name,
		workspace.Status,
		workspace.K8sNamespace,
		workspace.K8sSAName,
		workspace.DefaultClusterID,
		workspace.QuotaCPU,
		workspace.QuotaMemory,
		workspace.QuotaGPU,
		workspace.QuotaGPUMem,
		workspace.CreatedBy,
		workspace.UpdatedAt,
		workspace.ID,
	)
	if err != nil {
		return fmt.Errorf("failed to update workspace: %w", err)
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get affected rows: %w", err)
	}
	if rows == 0 {
		return entity.ErrWorkspaceNotFound
	}
	return nil
}

func (r *WorkspaceRepository) Delete(ctx context.Context, id string) error {
	result, err := r.db.conn.ExecContext(ctx, `DELETE FROM workspaces WHERE id = $1`, id)
	if err != nil {
		return fmt.Errorf("failed to delete workspace: %w", err)
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get affected rows: %w", err)
	}
	if rows == 0 {
		return entity.ErrWorkspaceNotFound
	}
	return nil
}

func (r *WorkspaceRepository) List(ctx context.Context) ([]*entity.Workspace, error) {
	query := `
		SELECT id, name, status, k8s_namespace, k8s_sa_name, default_cluster_id, quota_cpu, quota_memory, quota_gpu, quota_gpu_memory, created_by, created_at, updated_at
		FROM workspaces
		ORDER BY created_at DESC
	`
	rows, err := r.db.conn.QueryContext(ctx, query)
	if err != nil {
		return nil, fmt.Errorf("failed to list workspaces: %w", err)
	}
	defer rows.Close()
	workspaces := make([]*entity.Workspace, 0)
	for rows.Next() {
		workspace := &entity.Workspace{}
		var createdBy, defaultClusterID, quotaCPU, quotaMemory, quotaGPU, quotaGPUMem sql.NullString
		if err := rows.Scan(
			&workspace.ID,
			&workspace.Name,
			&workspace.Status,
			&workspace.K8sNamespace,
			&workspace.K8sSAName,
			&defaultClusterID,
			&quotaCPU,
			&quotaMemory,
			&quotaGPU,
			&quotaGPUMem,
			&createdBy,
			&workspace.CreatedAt,
			&workspace.UpdatedAt,
		); err != nil {
			return nil, fmt.Errorf("failed to scan workspace: %w", err)
		}
		workspace.CreatedBy = createdBy.String
		workspace.DefaultClusterID = defaultClusterID.String
		workspace.QuotaCPU = quotaCPU.String
		workspace.QuotaMemory = quotaMemory.String
		workspace.QuotaGPU = quotaGPU.String
		workspace.QuotaGPUMem = quotaGPUMem.String
		workspaces = append(workspaces, workspace)
	}
	return workspaces, rows.Err()
}

type WorkspaceClusterBindingRepository struct {
	db *DB
}

func NewWorkspaceClusterBindingRepository(db *DB) repository.WorkspaceClusterBindingRepository {
	return &WorkspaceClusterBindingRepository{db: db}
}

func (r *WorkspaceClusterBindingRepository) Upsert(ctx context.Context, binding *entity.WorkspaceClusterBinding) error {
	if binding.ID == "" {
		binding.ID = uuid.New().String()
	}
	now := time.Now()
	if binding.CreatedAt.IsZero() {
		binding.CreatedAt = now
	}
	binding.UpdatedAt = now
	query := `
		INSERT INTO workspace_cluster_bindings
			(id, workspace_id, cluster_id, namespace, service_account, quota_cpu, quota_memory, quota_gpu, quota_gpu_memory, status, created_at, updated_at)
		VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)
		ON CONFLICT (workspace_id, cluster_id)
		DO UPDATE SET namespace = EXCLUDED.namespace,
		              service_account = EXCLUDED.service_account,
		              quota_cpu = EXCLUDED.quota_cpu,
		              quota_memory = EXCLUDED.quota_memory,
		              quota_gpu = EXCLUDED.quota_gpu,
		              quota_gpu_memory = EXCLUDED.quota_gpu_memory,
		              status = EXCLUDED.status,
		              updated_at = EXCLUDED.updated_at
	`
	_, err := r.db.conn.ExecContext(ctx, query,
		binding.ID,
		binding.WorkspaceID,
		binding.ClusterID,
		binding.Namespace,
		binding.ServiceAccount,
		binding.QuotaCPU,
		binding.QuotaMemory,
		binding.QuotaGPU,
		binding.QuotaGPUMem,
		binding.Status,
		binding.CreatedAt,
		binding.UpdatedAt,
	)
	if err != nil {
		return fmt.Errorf("failed to upsert workspace cluster binding: %w", err)
	}
	return nil
}

func (r *WorkspaceClusterBindingRepository) Get(ctx context.Context, workspaceID, clusterID string) (*entity.WorkspaceClusterBinding, error) {
	query := `
		SELECT id, workspace_id, cluster_id, namespace, service_account, quota_cpu, quota_memory, quota_gpu, quota_gpu_memory, status, created_at, updated_at
		FROM workspace_cluster_bindings
		WHERE workspace_id = $1 AND cluster_id = $2
	`
	binding := &entity.WorkspaceClusterBinding{}
	err := r.db.conn.QueryRowContext(ctx, query, workspaceID, clusterID).Scan(
		&binding.ID,
		&binding.WorkspaceID,
		&binding.ClusterID,
		&binding.Namespace,
		&binding.ServiceAccount,
		&binding.QuotaCPU,
		&binding.QuotaMemory,
		&binding.QuotaGPU,
		&binding.QuotaGPUMem,
		&binding.Status,
		&binding.CreatedAt,
		&binding.UpdatedAt,
	)
	if err == sql.ErrNoRows {
		return nil, entity.ErrWorkspaceNotFound
	}
	if err != nil {
		return nil, fmt.Errorf("failed to get workspace cluster binding: %w", err)
	}
	return binding, nil
}

func (r *WorkspaceClusterBindingRepository) ListByWorkspace(ctx context.Context, workspaceID string) ([]*entity.WorkspaceClusterBinding, error) {
	query := `
		SELECT id, workspace_id, cluster_id, namespace, service_account, quota_cpu, quota_memory, quota_gpu, quota_gpu_memory, status, created_at, updated_at
		FROM workspace_cluster_bindings
		WHERE workspace_id = $1
		ORDER BY created_at ASC
	`
	rows, err := r.db.conn.QueryContext(ctx, query, workspaceID)
	if err != nil {
		return nil, fmt.Errorf("failed to list workspace cluster bindings: %w", err)
	}
	defer rows.Close()
	bindings := make([]*entity.WorkspaceClusterBinding, 0)
	for rows.Next() {
		binding := &entity.WorkspaceClusterBinding{}
		if err := rows.Scan(
			&binding.ID,
			&binding.WorkspaceID,
			&binding.ClusterID,
			&binding.Namespace,
			&binding.ServiceAccount,
			&binding.QuotaCPU,
			&binding.QuotaMemory,
			&binding.QuotaGPU,
			&binding.QuotaGPUMem,
			&binding.Status,
			&binding.CreatedAt,
			&binding.UpdatedAt,
		); err != nil {
			return nil, fmt.Errorf("failed to scan workspace cluster binding: %w", err)
		}
		bindings = append(bindings, binding)
	}
	return bindings, rows.Err()
}

func (r *WorkspaceClusterBindingRepository) Delete(ctx context.Context, workspaceID, clusterID string) error {
	_, err := r.db.conn.ExecContext(ctx, `DELETE FROM workspace_cluster_bindings WHERE workspace_id = $1 AND cluster_id = $2`, workspaceID, clusterID)
	return err
}

type AuditLogRepository struct {
	db *DB
}

func NewAuditLogRepository(db *DB) repository.AuditLogRepository {
	return &AuditLogRepository{db: db}
}

func (r *AuditLogRepository) Create(ctx context.Context, logEntry *entity.AuditLog) error {
	if logEntry.ID == "" {
		logEntry.ID = uuid.New().String()
	}
	details, err := json.Marshal(logEntry.Details)
	if err != nil {
		return fmt.Errorf("failed to marshal audit details: %w", err)
	}
	if logEntry.CreatedAt.IsZero() {
		logEntry.CreatedAt = time.Now()
	}
	query := `
		INSERT INTO audit_logs (id, workspace_id, user_id, action, resource_type, resource_id, resource_name, details, ip_address, user_agent, created_at)
		VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)
	`
	_, err = r.db.conn.ExecContext(ctx, query,
		logEntry.ID,
		logEntry.WorkspaceID,
		logEntry.UserID,
		logEntry.Action,
		logEntry.ResourceType,
		logEntry.ResourceID,
		logEntry.ResourceName,
		string(details),
		logEntry.IPAddress,
		logEntry.UserAgent,
		logEntry.CreatedAt,
	)
	if err != nil {
		return fmt.Errorf("failed to create audit log: %w", err)
	}
	return nil
}

func (r *AuditLogRepository) ListByWorkspace(ctx context.Context, workspaceID string, limit int) ([]*entity.AuditLog, error) {
	if limit <= 0 || limit > 500 {
		limit = 100
	}
	query := `
		SELECT id, workspace_id, user_id, action, resource_type, resource_id, resource_name, details, ip_address, user_agent, created_at
		FROM audit_logs
		WHERE workspace_id = $1
		ORDER BY created_at DESC
		LIMIT $2
	`
	rows, err := r.db.conn.QueryContext(ctx, query, workspaceID, limit)
	if err != nil {
		return nil, fmt.Errorf("failed to list audit logs: %w", err)
	}
	defer rows.Close()
	result := make([]*entity.AuditLog, 0)
	for rows.Next() {
		logEntry := &entity.AuditLog{}
		var details []byte
		if err := rows.Scan(
			&logEntry.ID,
			&logEntry.WorkspaceID,
			&logEntry.UserID,
			&logEntry.Action,
			&logEntry.ResourceType,
			&logEntry.ResourceID,
			&logEntry.ResourceName,
			&details,
			&logEntry.IPAddress,
			&logEntry.UserAgent,
			&logEntry.CreatedAt,
		); err != nil {
			return nil, fmt.Errorf("failed to scan audit log: %w", err)
		}
		_ = json.Unmarshal(details, &logEntry.Details)
		result = append(result, logEntry)
	}
	return result, rows.Err()
}
