package httpapi

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

	"github.com/dtoro/oikos/internal/adapters/postgres/sqlcgen"
	"github.com/dtoro/oikos/internal/core/domain"
	"github.com/dtoro/oikos/internal/httpapi/gen"
	"github.com/dtoro/oikos/internal/observability"
)

// ─── Entity Types (Ontology) ───────────────────────────────────────────

func (s *Server) CreateEntityType(ctx context.Context, req gen.CreateEntityTypeRequestObject) (gen.CreateEntityTypeResponseObject, error) {
	if req.Body == nil {
		return nil, fmt.Errorf("%w: request body is required", domain.ErrInvalidInput)
	}

	isAbstract := false
	if req.Body.IsAbstract != nil {
		isAbstract = *req.Body.IsAbstract
	}

	attrsSchemaJSON := []byte("null")
	if req.Body.AttributeSchema != nil {
		attrsSchemaJSON, _ = json.Marshal(req.Body.AttributeSchema)
	}

	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	_, err = tx.Exec(ctx, `
		INSERT INTO entity_types (name, parent_type, is_abstract, domain, layer, description, lifecycle_id, attribute_schema, status)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, 'active')`,
		req.Body.Name, req.Body.ParentType, isAbstract, req.Body.Domain,
		string(req.Body.Layer), req.Body.Description, req.Body.LifecycleId, attrsSchemaJSON)
	if err != nil {
		if strings.Contains(err.Error(), "unique") || strings.Contains(err.Error(), "duplicate") {
			return nil, fmt.Errorf("%w: entity type %q already exists", domain.ErrAlreadyExists, req.Body.Name)
		}
		return nil, err
	}

	// Re-read.
	var et gen.EntityType
	var schemaBytes []byte
	err = tx.QueryRow(ctx, `
		SELECT name, parent_type, is_abstract, domain, layer, description,
		       lifecycle_id, attribute_schema, schema_version, status
		FROM entity_types WHERE name = $1`, req.Body.Name).
		Scan(&et.Name, &et.ParentType, &et.IsAbstract, &et.Domain, &et.Layer,
			&et.Description, &et.LifecycleId, &schemaBytes, &et.SchemaVersion, &et.Status)
	if err != nil {
		return nil, err
	}
	var schema map[string]any
	if len(schemaBytes) > 0 && json.Unmarshal(schemaBytes, &schema) == nil && schema != nil {
		et.AttributeSchema = &schema
	}

	actorType, actor := actorInfo(ctx)
	if auditErr := observability.Audit(ctx, sqlcgen.New(tx), actorType, actor, "create",
		nil, "POST", "/api/v1/ontology/entity-types", "",
		nil,
		map[string]any{"name": req.Body.Name, "domain": req.Body.Domain}); auditErr != nil {
		return nil, auditErr
	}

	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}

	return gen.CreateEntityType201JSONResponse(et), nil
}

func (s *Server) PatchEntityType(ctx context.Context, req gen.PatchEntityTypeRequestObject) (gen.PatchEntityTypeResponseObject, error) {
	if req.Body == nil {
		return nil, fmt.Errorf("%w: request body is required", domain.ErrInvalidInput)
	}

	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	// Build dynamic update.
	sets := []string{}
	args := []any{}
	argIdx := 2

	if req.Body.Description != nil {
		sets = append(sets, fmt.Sprintf("description = $%d", argIdx))
		args = append(args, *req.Body.Description)
		argIdx++
	}
	if req.Body.Status != nil {
		sets = append(sets, fmt.Sprintf("status = $%d", argIdx))
		args = append(args, string(*req.Body.Status))
		argIdx++
	}
	if req.Body.AttributeSchema != nil {
		schemaJSON, _ := json.Marshal(req.Body.AttributeSchema)
		sets = append(sets, fmt.Sprintf("attribute_schema = $%d", argIdx))
		args = append(args, schemaJSON)
		argIdx++
	}

	if len(sets) == 0 {
		return nil, fmt.Errorf("%w: no fields to update", domain.ErrInvalidInput)
	}

	sets = append(sets, "schema_version = schema_version + 1, updated_at = now()")

	query := fmt.Sprintf(`UPDATE entity_types SET %s WHERE name = $1`, strings.Join(sets, ", "))
	finalArgs := append([]any{req.Name}, args...)

	result, err := tx.Exec(ctx, query, finalArgs...)
	if err != nil {
		return nil, err
	}
	if result.RowsAffected() == 0 {
		return nil, fmt.Errorf("%w: entity type %q", domain.ErrNotFound, req.Name)
	}

	// Re-read.
	var et gen.EntityType
	var schemaBytes []byte
	err = tx.QueryRow(ctx, `
		SELECT name, parent_type, is_abstract, domain, layer, description,
		       lifecycle_id, attribute_schema, schema_version, status
		FROM entity_types WHERE name = $1`, req.Name).
		Scan(&et.Name, &et.ParentType, &et.IsAbstract, &et.Domain, &et.Layer,
			&et.Description, &et.LifecycleId, &schemaBytes, &et.SchemaVersion, &et.Status)
	if err != nil {
		return nil, err
	}
	var schema map[string]any
	if len(schemaBytes) > 0 && json.Unmarshal(schemaBytes, &schema) == nil && schema != nil {
		et.AttributeSchema = &schema
	}

	actorType, actor := actorInfo(ctx)
	if auditErr := observability.Audit(ctx, sqlcgen.New(tx), actorType, actor, "patch",
		nil, "PATCH", "/api/v1/ontology/entity-types/"+req.Name, "",
		nil,
		map[string]any{"status": req.Body.Status}); auditErr != nil {
		return nil, auditErr
	}

	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}

	return gen.PatchEntityType200JSONResponse(et), nil
}
