package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	appErrors "github.com/nexuscrm/backend/pkg/errors"
)

// ============================================================================
// Go Struct Generation
// ============================================================================

func generateGoStructs(ctx *genContext, projectRoot string) error {
	var sb strings.Builder

	sb.WriteString("// Code generated by cmd/codegen. DO NOT EDIT.\n")
	sb.WriteString("// Source: internal/bootstrap/system_tables.json\n")
	sb.WriteString("// Generated at: " + ctx.timestamp + "\n\n")
	sb.WriteString("//go:generate go run ../../../cmd/codegen\n\n")
	sb.WriteString("package models\n\n")
	sb.WriteString("import (\n")
	sb.WriteString("\t\"encoding/json\"\n")
	sb.WriteString("\t\"time\"\n")
	sb.WriteString(")\n\n")

	// Suppress unused import warning
	sb.WriteString("// Ensure imports are used\n")
	sb.WriteString("var (\n")
	sb.WriteString("\t_ json.RawMessage\n")
	sb.WriteString("\t_ time.Time\n")
	sb.WriteString(")\n\n")

	// Sort tables for deterministic output
	for _, t := range ctx.tables {
		// Skip tables with manual struct definitions (controlled via JSON)
		if t.SkipStructGen {
			continue
		}
		structName := tableNameToStructName(t.TableName) // Removed Gen prefix for direct usage

		// Add description as comment
		if t.Description != "" {
			sb.WriteString(fmt.Sprintf("// %s represents the %s table (generated).\n", structName, t.TableName))
			sb.WriteString(fmt.Sprintf("// %s\n", t.Description))
		} else {
			sb.WriteString(fmt.Sprintf("// %s represents the %s table (generated).\n", structName, t.TableName))
		}

		sb.WriteString(fmt.Sprintf("type %s struct {\n", structName))

		for _, col := range dedupeColumns(t.Columns, func(col ColumnDef) string {
			return snakeToPascal(col.Name)
		}) {
			fieldName := snakeToPascal(col.Name)
			goType := sqlTypeToGoType(col.Type, col.Nullable)
			jsonTag := col.Name

			// JSON tag directly uses the physical column name because it matches the logical name

			omitEmpty := col.Nullable && !col.PrimaryKey

			// Passwords and internal ownership markers remain available to Go
			// callers but must never leak through generated JSON projections.
			if col.LogicalType == "password" || col.LogicalType == "internal" {
				sb.WriteString(fmt.Sprintf("\t%s %s `json:\"-\"`\n", fieldName, goType))
			} else if omitEmpty {
				sb.WriteString(fmt.Sprintf("\t%s %s `json:\"%s,omitempty\"`\n", fieldName, goType, jsonTag))
			} else {
				sb.WriteString(fmt.Sprintf("\t%s %s `json:\"%s\"`\n", fieldName, goType, jsonTag))
			}
		}

		sb.WriteString("}\n\n")

		// Generate GetTableName method (using Get prefix to avoid conflict with TableName field)
		sb.WriteString(fmt.Sprintf("// GetTableName returns the database table name for %s.\n", structName))
		sb.WriteString(fmt.Sprintf("func (%s) GetTableName() string {\n", structName))
		sb.WriteString(fmt.Sprintf("\treturn \"%s\"\n", t.TableName))
		sb.WriteString("}\n\n")
	}

	outPath := filepath.Join(projectRoot, "shared", "pkg", "models", "z_generated.go")
	if err := os.WriteFile(outPath, []byte(sb.String()), 0644); err != nil {
		return appErrors.NewInternalError("write z_generated.go", err)
	}
	fmt.Printf("✅ Generated: %s (%d bytes)\n", outPath, sb.Len())
	return nil
}
