package db

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

	"gopkg.in/yaml.v3"
)

// ExportToYAML regenerates the three seed YAMLs from the DB (DR / version
// control, plan D6). Output is deterministic: maps marshal with sorted keys
// (yaml.v3 default for map[string]any) and lists are ordered by slug/name,
// so export → ingest → export is byte-stable.
//
// Cognition-layer entities (signals, executions, patterns, …) are runtime
// state, not inventory — they are excluded from the export.
func ExportToYAML(ctx context.Context, pool *Pool) (map[string][]byte, error) {
	result := make(map[string][]byte)

	for name, fn := range map[string]func(context.Context, *Pool) (map[string]any, error){
		"ontology.yaml":  exportOntology,
		"inventory.yaml": exportInventory,
		"policy.yaml":    exportPolicy,
	} {
		doc, err := fn(ctx, pool)
		if err != nil {
			return nil, fmt.Errorf("export %s: %w", name, err)
		}
		out, err := yaml.Marshal(doc)
		if err != nil {
			return nil, fmt.Errorf("marshal %s: %w", name, err)
		}
		result[name] = out
	}
	return result, nil
}

func exportOntology(ctx context.Context, pool *Pool) (map[string]any, error) {
	lifecycles := map[string]any{}
	rows, err := pool.Query(ctx,
		`SELECT id, states, default_state, terminal_states, transitions
		 FROM lifecycle_defs ORDER BY id`)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var id, def string
		var states, terminal []string
		var transitionsJSON []byte
		if err := rows.Scan(&id, &states, &def, &terminal, &transitionsJSON); err != nil {
			rows.Close()
			return nil, err
		}
		var transitions map[string]any
		if err := json.Unmarshal(transitionsJSON, &transitions); err != nil {
			rows.Close()
			return nil, fmt.Errorf("lifecycle %s transitions: %w", id, err)
		}
		lifecycles[id] = map[string]any{
			"states":          states,
			"default_state":   def,
			"terminal_states": terminal,
			"transitions":     transitions,
		}
	}
	rows.Close()
	if rows.Err() != nil {
		return nil, rows.Err()
	}

	entityTypes := map[string]any{}
	rows, err = pool.Query(ctx,
		`SELECT name, COALESCE(parent_type,''), is_abstract, domain, layer,
		        COALESCE(description,''), COALESCE(lifecycle_id,''), attribute_schema
		 FROM entity_types ORDER BY name`)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var name, parent, dom, layer, desc, lc string
		var isAbstract bool
		var schemaJSON []byte
		if err := rows.Scan(&name, &parent, &isAbstract, &dom, &layer, &desc, &lc, &schemaJSON); err != nil {
			rows.Close()
			return nil, err
		}
		et := map[string]any{"domain": dom, "layer": layer}
		if parent != "" {
			et["parent"] = parent
		}
		if isAbstract {
			et["abstract"] = true
		}
		if desc != "" {
			et["description"] = desc
		}
		if lc != "" {
			et["lifecycle"] = lc
		}
		if len(schemaJSON) > 0 && string(schemaJSON) != "null" {
			var schema map[string]any
			if err := json.Unmarshal(schemaJSON, &schema); err == nil && schema != nil {
				et["attributes"] = schema
			}
		}
		entityTypes[name] = et
	}
	rows.Close()
	if rows.Err() != nil {
		return nil, rows.Err()
	}

	relTypes := map[string]any{}
	rows, err = pool.Query(ctx,
		`SELECT name, COALESCE(inverse,''), source_type, target_type, cardinality,
		        COALESCE(description,'')
		 FROM relationship_types ORDER BY name`)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var name, inverse, src, tgt, card, desc string
		if err := rows.Scan(&name, &inverse, &src, &tgt, &card, &desc); err != nil {
			rows.Close()
			return nil, err
		}
		rt := map[string]any{"source": src, "target": tgt, "cardinality": card}
		if inverse != "" {
			rt["inverse"] = inverse
		}
		if desc != "" {
			rt["description"] = desc
		}
		relTypes[name] = rt
	}
	rows.Close()
	if rows.Err() != nil {
		return nil, rows.Err()
	}

	return map[string]any{
		"version":            1,
		"lifecycles":         lifecycles,
		"entity_types":       entityTypes,
		"relationship_types": relTypes,
	}, nil
}

func exportInventory(ctx context.Context, pool *Pool) (map[string]any, error) {
	var entities []any
	rows, err := pool.Query(ctx, `
		SELECT e.slug, e.type, e.name, COALESCE(e.state,''), e.attributes
		FROM entities e
		JOIN entity_types et ON et.name = e.type
		WHERE et.layer <> 'cognition'
		ORDER BY e.slug`)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var slug, typ, name, state string
		var attrsJSON []byte
		if err := rows.Scan(&slug, &typ, &name, &state, &attrsJSON); err != nil {
			rows.Close()
			return nil, err
		}
		e := map[string]any{"slug": slug, "type": typ, "name": name}
		if state != "" {
			e["state"] = state
		}
		var attrs map[string]any
		if err := json.Unmarshal(attrsJSON, &attrs); err == nil && len(attrs) > 0 {
			e["attributes"] = attrs
		}
		entities = append(entities, e)
	}
	rows.Close()
	if rows.Err() != nil {
		return nil, rows.Err()
	}

	var rels []any
	rows, err = pool.Query(ctx, `
		SELECT se.slug, te.slug, r.type, r.attributes
		FROM relationships r
		JOIN entities se ON se.id = r.source_id
		JOIN entities te ON te.id = r.target_id
		WHERE r.valid_to IS NULL
		ORDER BY r.type, se.slug, te.slug`)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var src, tgt, typ string
		var attrsJSON []byte
		if err := rows.Scan(&src, &tgt, &typ, &attrsJSON); err != nil {
			rows.Close()
			return nil, err
		}
		rel := map[string]any{"source": src, "target": tgt, "type": typ}
		var attrs map[string]any
		if len(attrsJSON) > 0 && json.Unmarshal(attrsJSON, &attrs) == nil && len(attrs) > 0 {
			rel["attributes"] = attrs
		}
		rels = append(rels, rel)
	}
	rows.Close()
	if rows.Err() != nil {
		return nil, rows.Err()
	}

	return map[string]any{
		"version":       1,
		"entities":      entities,
		"relationships": rels,
	}, nil
}

func exportPolicy(ctx context.Context, pool *Pool) (map[string]any, error) {
	riskClasses := map[string]any{}
	rows, err := pool.Query(ctx,
		`SELECT name, COALESCE(description,''), approval_required, autonomy_allowed
		 FROM risk_classes ORDER BY name`)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var name, desc, approval string
		var autonomy bool
		if err := rows.Scan(&name, &desc, &approval, &autonomy); err != nil {
			rows.Close()
			return nil, err
		}
		rc := map[string]any{"approval_required": approval, "autonomy_allowed": autonomy}
		if desc != "" {
			rc["description"] = desc
		}
		riskClasses[name] = rc
	}
	rows.Close()
	if rows.Err() != nil {
		return nil, rows.Err()
	}

	var rules []any
	rows, err = pool.Query(ctx, `
		SELECT COALESCE(ar.entity_type,''), ar.action, ar.risk_class,
		       ar.autonomy_level, COALESCE(se.slug,'')
		FROM approval_rules ar
		LEFT JOIN entities se ON se.id = ar.scope_entity
		ORDER BY COALESCE(ar.entity_type,''), ar.action, COALESCE(se.slug,'')`)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var et, action, rc, al, scope string
		if err := rows.Scan(&et, &action, &rc, &al, &scope); err != nil {
			rows.Close()
			return nil, err
		}
		rule := map[string]any{"action": action, "risk_class": rc, "autonomy_level": al}
		if et != "" {
			rule["entity_type"] = et
		}
		if scope != "" {
			rule["scope_entity"] = scope
		}
		rules = append(rules, rule)
	}
	rows.Close()
	if rows.Err() != nil {
		return nil, rows.Err()
	}

	settings := map[string]any{}
	rows, err = pool.Query(ctx, `SELECT key, value FROM autonomy_settings ORDER BY key`)
	if err != nil {
		return nil, err
	}
	for rows.Next() {
		var k, v string
		if err := rows.Scan(&k, &v); err != nil {
			rows.Close()
			return nil, err
		}
		settings[k] = v
	}
	rows.Close()
	if rows.Err() != nil {
		return nil, rows.Err()
	}

	return map[string]any{
		"version":           1,
		"risk_classes":      riskClasses,
		"approval_rules":    rules,
		"autonomy_settings": settings,
	}, nil
}
