// Package service provides database and model services.
// Mirrors: odoo/service/db.py
package service

import (
	"context"
	"crypto/rand"
	"fmt"
	"log"
	"strings"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"

	l10n_de "odoo-go/addons/l10n_de"
	"odoo-go/pkg/orm"
	"odoo-go/pkg/tools"
)

// InitDatabase creates all tables for registered models.
// Mirrors: odoo/modules/loading.py load_module_graph() → model._setup_base()
func InitDatabase(ctx context.Context, pool *pgxpool.Pool) error {
	tx, err := pool.Begin(ctx)
	if err != nil {
		return fmt.Errorf("db: begin: %w", err)
	}
	defer tx.Rollback(ctx)

	models := orm.Registry.Models()

	// Phase 1: Create tables
	for name, m := range models {
		if m.IsAbstract() {
			continue
		}
		sql := m.CreateTableSQL()
		if sql == "" {
			continue
		}
		log.Printf("db: creating table for %s", name)
		if _, err := tx.Exec(ctx, sql); err != nil {
			return fmt.Errorf("db: create table %s: %w", name, err)
		}
	}

	// Phase 2: Add foreign keys (after all tables exist, each in savepoint)
	for name, m := range models {
		if m.IsAbstract() {
			continue
		}
		for _, sql := range m.ForeignKeySQL() {
			sp, spErr := tx.Begin(ctx)
			if spErr != nil {
				continue
			}
			wrappedSQL := fmt.Sprintf(
				`DO $$ BEGIN %s; EXCEPTION WHEN duplicate_object THEN NULL; END $$`,
				sql,
			)
			if _, err := sp.Exec(ctx, wrappedSQL); err != nil {
				log.Printf("db: warning: FK for %s: %v", name, err)
				sp.Rollback(ctx)
			} else {
				log.Printf("db: adding FK for %s", name)
				sp.Commit(ctx)
			}
		}
	}

	// Phase 3: Create indexes (each in savepoint)
	for _, m := range models {
		if m.IsAbstract() {
			continue
		}
		for _, sql := range m.IndexSQL() {
			sp, spErr := tx.Begin(ctx)
			if spErr != nil {
				continue
			}
			if _, err := sp.Exec(ctx, sql); err != nil {
				log.Printf("db: warning: index: %v", err)
				sp.Rollback(ctx)
			} else {
				sp.Commit(ctx)
			}
		}
	}

	// Phase 4: Create Many2many junction tables (each in savepoint to avoid aborting tx)
	for _, m := range models {
		if m.IsAbstract() {
			continue
		}
		for _, sql := range m.Many2manyTableSQL() {
			sp, spErr := tx.Begin(ctx)
			if spErr != nil {
				continue
			}
			if _, err := sp.Exec(ctx, sql); err != nil {
				log.Printf("db: warning: m2m table: %v", err)
				sp.Rollback(ctx)
			} else {
				sp.Commit(ctx)
			}
		}
	}

	// Phase 4b: Add unique constraint on ir_config_parameter.key for ON CONFLICT support.
	// Mirrors: odoo/addons/base/models/ir_config_parameter.py _sql_constraints
	sp, spErr := tx.Begin(ctx)
	if spErr == nil {
		if _, err := sp.Exec(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS ir_config_parameter_key_uniq ON ir_config_parameter (key)`); err != nil {
			sp.Rollback(ctx)
		} else {
			sp.Commit(ctx)
		}
	}

	// Phase 4c: SQL constraints
	// Mirrors: _sql_constraints in Odoo models
	for name, m := range models {
		if m.IsAbstract() {
			continue
		}
		for _, sc := range m.SQLConstraints {
			constraintName := strings.ReplaceAll(name, ".", "_") + "_" + sc.Name
			query := fmt.Sprintf(
				`DO $$ BEGIN
					ALTER TABLE %q ADD CONSTRAINT %s %s;
				EXCEPTION WHEN duplicate_object THEN NULL;
				END $$`,
				m.Table(), constraintName, sc.Definition,
			)
			sp, spErr := tx.Begin(ctx)
			if spErr != nil {
				continue
			}
			if _, err := sp.Exec(ctx, query); err != nil {
				log.Printf("db: constraint %s: %v", constraintName, err)
				sp.Rollback(ctx)
			} else {
				sp.Commit(ctx)
			}
		}
	}

	// Phase 5: Seed ir_model and ir_model_fields with model metadata.
	// This is critical because ir.rule joins through ir_model to find rules for a model.
	// Mirrors: odoo/modules/loading.py load_module_graph() → _setup_base()
	seedIrModelMetadata(ctx, tx, models)

	if err := tx.Commit(ctx); err != nil {
		return fmt.Errorf("db: commit: %w", err)
	}

	log.Printf("db: schema initialized for %d models", len(models))
	return nil
}

// seedIrModelMetadata populates ir_model and ir_model_fields for all registered models.
// Each model gets a row in ir_model; each field gets a row in ir_model_fields.
// Uses ON CONFLICT DO NOTHING so it's safe to call on every startup.
func seedIrModelMetadata(ctx context.Context, tx pgx.Tx, models map[string]*orm.Model) {
	// Check if ir_model table exists (it should, but guard against ordering issues)
	var exists bool
	err := tx.QueryRow(ctx,
		`SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ir_model')`,
	).Scan(&exists)
	if err != nil || !exists {
		log.Println("db: ir_model table does not exist yet, skipping metadata seed")
		return
	}

	// Also check ir_model_fields
	var fieldsExists bool
	err = tx.QueryRow(ctx,
		`SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'ir_model_fields')`,
	).Scan(&fieldsExists)
	if err != nil || !fieldsExists {
		log.Println("db: ir_model_fields table does not exist yet, skipping metadata seed")
		return
	}

	modelCount := 0
	fieldCount := 0

	for name, m := range models {
		if m.IsAbstract() {
			continue
		}

		// Check if this model already exists in ir_model
		var modelID int64
		sp, spErr := tx.Begin(ctx)
		if spErr != nil {
			continue
		}
		err := sp.QueryRow(ctx,
			`SELECT id FROM ir_model WHERE model = $1`, name,
		).Scan(&modelID)
		if err != nil {
			// Model doesn't exist yet — insert it
			sp.Rollback(ctx)
			sp2, spErr2 := tx.Begin(ctx)
			if spErr2 != nil {
				continue
			}
			err = sp2.QueryRow(ctx,
				`INSERT INTO ir_model (model, name, info, state, transient)
				 VALUES ($1, $2, $3, 'base', $4)
				 RETURNING id`,
				name, m.Description(), m.Description(), m.IsTransient(),
			).Scan(&modelID)
			if err != nil {
				sp2.Rollback(ctx)
				continue
			}
			sp2.Commit(ctx)
			modelCount++
		} else {
			sp.Commit(ctx)
		}

		if modelID == 0 {
			continue
		}

		// INSERT into ir_model_fields for each field
		for fieldName, field := range m.Fields() {
			if fieldName == "id" || fieldName == "display_name" {
				continue
			}

			// Check if this field already exists
			sp, spErr := tx.Begin(ctx)
			if spErr != nil {
				continue
			}
			var fieldExists bool
			err := sp.QueryRow(ctx,
				`SELECT EXISTS(SELECT 1 FROM ir_model_fields WHERE model_id = $1 AND name = $2)`,
				modelID, fieldName,
			).Scan(&fieldExists)
			if err != nil {
				sp.Rollback(ctx)
				continue
			}
			if fieldExists {
				sp.Commit(ctx)
				continue
			}
			sp.Commit(ctx)

			sp2, spErr2 := tx.Begin(ctx)
			if spErr2 != nil {
				continue
			}
			_, err = sp2.Exec(ctx,
				`INSERT INTO ir_model_fields (model_id, name, field_description, ttype, state, store)
				 VALUES ($1, $2, $3, $4, 'base', $5)`,
				modelID, fieldName, field.String, field.Type.String(), field.IsStored(),
			)
			if err != nil {
				sp2.Rollback(ctx)
			} else {
				sp2.Commit(ctx)
				fieldCount++
			}
		}
	}

	log.Printf("db: seeded ir_model metadata: %d models, %d fields", modelCount, fieldCount)
}

// NeedsSetup checks if the database requires initial setup.
func NeedsSetup(ctx context.Context, pool *pgxpool.Pool) bool {
	var count int
	err := pool.QueryRow(ctx, `SELECT COUNT(*) FROM res_company`).Scan(&count)
	return err != nil || count == 0
}

// SetupConfig holds parameters for the setup wizard.
type SetupConfig struct {
	CompanyName   string
	Street        string
	Zip           string
	City          string
	CountryCode   string
	CountryName   string
	PhoneCode     string
	Email         string
	Phone         string
	VAT           string
	Chart         string // "skr03", "skr04", "none"
	AdminLogin    string
	AdminPassword string // Already hashed with bcrypt
	DemoData      bool
}

// SeedWithSetup initializes the database with user-provided configuration.
func SeedWithSetup(ctx context.Context, pool *pgxpool.Pool, cfg SetupConfig) error {
	tx, err := pool.Begin(ctx)
	if err != nil {
		return fmt.Errorf("db: begin: %w", err)
	}
	defer tx.Rollback(ctx)

	log.Printf("db: seeding database for %q...", cfg.CompanyName)

	if err := seedCurrencyAndCountry(ctx, tx, cfg); err != nil {
		return err
	}
	if err := seedCompanyAndAdmin(ctx, tx, cfg); err != nil {
		return err
	}
	if err := seedJournalsAndSequences(ctx, tx); err != nil {
		return err
	}
	seedChartOfAccounts(ctx, tx, cfg)
	seedStockData(ctx, tx)
	seedViews(ctx, tx)
	seedAccountReports(ctx, tx)
	seedActions(ctx, tx)
	seedMenus(ctx, tx)

	// Settings record (res.config.settings needs at least one record to display)
	tx.Exec(ctx, `INSERT INTO res_config_settings (id, company_id, show_effect, create_uid, write_uid)
		VALUES (1, 1, true, 1, 1) ON CONFLICT (id) DO NOTHING`)

	seedSystemParams(ctx, tx)
	seedLanguages(ctx, tx)
	seedTranslations(ctx, tx)

	if cfg.DemoData {
		seedDemoData(ctx, tx)
	}

	resetSequences(ctx, tx)

	if err := tx.Commit(ctx); err != nil {
		return fmt.Errorf("db: commit seed: %w", err)
	}

	log.Printf("db: database seeded successfully for %q", cfg.CompanyName)
	return nil
}

// seedCurrencyAndCountry seeds the default currency (EUR) and country.
func seedCurrencyAndCountry(ctx context.Context, tx pgx.Tx, cfg SetupConfig) error {
	_, err := tx.Exec(ctx, `
		INSERT INTO res_currency (id, name, symbol, decimal_places, rounding, active, "position")
		VALUES (1, 'EUR', '€', 2, 0.01, true, 'after')
		ON CONFLICT (id) DO NOTHING`)
	if err != nil {
		return fmt.Errorf("db: seed currency: %w", err)
	}

	_, err = tx.Exec(ctx, `
		INSERT INTO res_country (id, name, code, phone_code)
		VALUES (1, $1, $2, $3)
		ON CONFLICT (id) DO NOTHING`, cfg.CountryName, cfg.CountryCode, cfg.PhoneCode)
	if err != nil {
		return fmt.Errorf("db: seed country: %w", err)
	}
	return nil
}

// seedCompanyAndAdmin seeds the company partner, company, admin partner, and admin user.
func seedCompanyAndAdmin(ctx context.Context, tx pgx.Tx, cfg SetupConfig) error {
	_, err := tx.Exec(ctx, `
		INSERT INTO res_partner (id, name, is_company, active, type, lang, email, phone, street, zip, city, country_id, vat)
		VALUES (1, $1, true, true, 'contact', 'de_DE', $2, $3, $4, $5, $6, 1, $7)
		ON CONFLICT (id) DO NOTHING`,
		cfg.CompanyName, cfg.Email, cfg.Phone, cfg.Street, cfg.Zip, cfg.City, cfg.VAT)
	if err != nil {
		return fmt.Errorf("db: seed company partner: %w", err)
	}

	_, err = tx.Exec(ctx, `
		INSERT INTO res_company (id, name, partner_id, currency_id, country_id, active, sequence, street, zip, city, email, phone, vat)
		VALUES (1, $1, 1, 1, 1, true, 10, $2, $3, $4, $5, $6, $7)
		ON CONFLICT (id) DO NOTHING`,
		cfg.CompanyName, cfg.Street, cfg.Zip, cfg.City, cfg.Email, cfg.Phone, cfg.VAT)
	if err != nil {
		return fmt.Errorf("db: seed company: %w", err)
	}

	adminName := "Administrator"
	_, err = tx.Exec(ctx, `
		INSERT INTO res_partner (id, name, is_company, active, type, email, lang)
		VALUES (2, $1, false, true, 'contact', $2, 'de_DE')
		ON CONFLICT (id) DO NOTHING`, adminName, cfg.AdminLogin)
	if err != nil {
		return fmt.Errorf("db: seed admin partner: %w", err)
	}

	_, err = tx.Exec(ctx, `
		INSERT INTO res_users (id, login, password, active, partner_id, company_id)
		VALUES (1, $1, $2, true, 2, 1)
		ON CONFLICT (id) DO NOTHING`, cfg.AdminLogin, cfg.AdminPassword)
	if err != nil {
		return fmt.Errorf("db: seed admin user: %w", err)
	}
	return nil
}

// seedJournalsAndSequences seeds accounting journals and IR sequences.
func seedJournalsAndSequences(ctx context.Context, tx pgx.Tx) error {
	_, err := tx.Exec(ctx, `
		INSERT INTO account_journal (id, name, code, type, company_id, active, sequence) VALUES
			(1, 'Ausgangsrechnungen', 'INV', 'sale', 1, true, 10),
			(2, 'Eingangsrechnungen', 'BILL', 'purchase', 1, true, 20),
			(3, 'Bank', 'BNK1', 'bank', 1, true, 30),
			(4, 'Kasse', 'CSH1', 'cash', 1, true, 40),
			(5, 'Sonstige', 'MISC', 'general', 1, true, 50)
		ON CONFLICT (id) DO NOTHING`)
	if err != nil {
		return fmt.Errorf("db: seed journals: %w", err)
	}

	_, err = tx.Exec(ctx, `
		INSERT INTO ir_sequence (id, name, code, prefix, padding, number_next, number_increment, active, implementation) VALUES
			(1, 'Buchungssatz', 'account.move', 'MISC/', 4, 1, 1, true, 'standard'),
			(2, 'Ausgangsrechnung', 'account.move.out_invoice', 'RE/%(year)s/', 4, 1, 1, true, 'standard'),
			(3, 'Eingangsrechnung', 'account.move.in_invoice', 'ER/%(year)s/', 4, 1, 1, true, 'standard'),
			(4, 'Angebot', 'sale.order', 'AG', 4, 1, 1, true, 'standard'),
			(5, 'Bestellung', 'purchase.order', 'BE', 4, 1, 1, true, 'standard')
		ON CONFLICT (id) DO NOTHING`)
	if err != nil {
		return fmt.Errorf("db: seed sequences: %w", err)
	}
	return nil
}

// seedChartOfAccounts seeds the chart of accounts and tax definitions if configured.
func seedChartOfAccounts(ctx context.Context, tx pgx.Tx, cfg SetupConfig) {
	if cfg.Chart != "skr03" && cfg.Chart != "skr04" {
		return
	}
	// Currently only SKR03 is implemented
	for _, acc := range l10n_de.SKR03Accounts {
		tx.Exec(ctx, `
			INSERT INTO account_account (code, name, account_type, company_id, reconcile)
			VALUES ($1, $2, $3, 1, $4) ON CONFLICT DO NOTHING`,
			acc.Code, acc.Name, acc.AccountType, acc.Reconcile)
	}
	log.Printf("db: seeded %d SKR03 accounts", len(l10n_de.SKR03Accounts))

	for _, tax := range l10n_de.SKR03Taxes {
		tx.Exec(ctx, `
			INSERT INTO account_tax (name, amount, type_tax_use, amount_type, company_id, active, sequence, is_base_affected)
			VALUES ($1, $2, $3, 'percent', 1, true, 1, true) ON CONFLICT DO NOTHING`,
			tax.Name, tax.Amount, tax.TypeUse)
	}
	log.Printf("db: seeded %d German tax definitions", len(l10n_de.SKR03Taxes))
}

// resetSequences resets all auto-increment sequences to their current max values.
func resetSequences(ctx context.Context, tx pgx.Tx) {
	seqs := []string{
		"res_currency", "res_country", "res_partner", "res_company",
		"res_users", "ir_sequence", "account_journal", "account_account",
		"account_tax", "sale_order", "sale_order_line", "account_move",
		"ir_act_window", "ir_model_data", "ir_ui_menu",
		"stock_location", "stock_picking_type", "stock_warehouse",
		"crm_stage", "crm_lead",
		"ir_config_parameter",
		"ir_translation", "ir_act_report", "res_lang",
		"product_template", "product_product",
		"hr_department", "hr_employee",
		"project_project",
	}
	for _, table := range seqs {
		tx.Exec(ctx, fmt.Sprintf(
			`SELECT setval('%s_id_seq', GREATEST((SELECT COALESCE(MAX(id),0) FROM %q), 1))`,
			table, table))
	}
}

// seedStockData creates stock locations, picking types, and a default warehouse.
// Mirrors: odoo/addons/stock/data/stock_data.xml
func seedStockData(ctx context.Context, tx pgx.Tx) {
	log.Println("db: seeding stock reference data...")

	// Stock locations (hierarchical, mirroring Odoo's stock_data.xml)
	tx.Exec(ctx, `
		INSERT INTO stock_location (id, name, complete_name, usage, active, location_id, company_id) VALUES
			(1, 'Physical Locations', 'Physical Locations', 'view', true, NULL, 1),
			(2, 'Partner Locations', 'Partner Locations', 'view', true, NULL, 1),
			(3, 'Virtual Locations', 'Virtual Locations', 'view', true, NULL, 1),
			(4, 'WH', 'Physical Locations/WH', 'internal', true, 1, 1),
			(5, 'Customers', 'Partner Locations/Customers', 'customer', true, 2, 1),
			(6, 'Vendors', 'Partner Locations/Vendors', 'supplier', true, 2, 1),
			(7, 'Scrap', 'Virtual Locations/Scrap', 'inventory', true, 3, 1)
		ON CONFLICT (id) DO NOTHING`)

	// Default warehouse (must come before picking types due to FK)
	tx.Exec(ctx, `
		INSERT INTO stock_warehouse (id, name, code, active, company_id, lot_stock_id, sequence)
		VALUES (1, 'Main Warehouse', 'WH', true, 1, 4, 10)
		ON CONFLICT (id) DO NOTHING`)

	// Stock picking types (operation types for receipts, deliveries, internal transfers)
	tx.Exec(ctx, `
		INSERT INTO stock_picking_type (id, name, code, sequence_code, active, warehouse_id, company_id, sequence,
			default_location_src_id, default_location_dest_id) VALUES
			(1, 'Receipts', 'incoming', 'IN', true, 1, 1, 1, 6, 4),
			(2, 'Delivery Orders', 'outgoing', 'OUT', true, 1, 1, 2, 4, 5),
			(3, 'Internal Transfers', 'internal', 'INT', true, 1, 1, 3, 4, 4)
		ON CONFLICT (id) DO NOTHING`)

	log.Println("db: stock reference data seeded (6 locations, 3 picking types, 1 warehouse)")
}

// seedViews creates UI views for key models.
func seedViews(ctx context.Context, tx pgx.Tx) {
	log.Println("db: seeding UI views...")

	tx.Exec(ctx, `INSERT INTO ir_ui_view (name, model, type, arch, priority, active, mode) VALUES
		('partner.list', 'res.partner', 'list', '<list>
  <field name="name"/>
  <field name="email"/>
  <field name="phone"/>
  <field name="city"/>
  <field name="country_id"/>
  <field name="is_company"/>
</list>', 16, true, 'primary'),
		('partner.form', 'res.partner', 'form', '<form>
  <sheet>
    <div class="oe_title"><h1><field name="name" placeholder="Name"/></h1></div>
    <group>
      <group>
        <field name="is_company"/>
        <field name="type"/>
        <field name="email"/>
        <field name="phone"/>
        <field name="mobile"/>
        <field name="website"/>
      </group>
      <group>
        <field name="street"/>
        <field name="street2"/>
        <field name="zip"/>
        <field name="city"/>
        <field name="country_id"/>
        <field name="vat"/>
        <field name="lang"/>
      </group>
    </group>
    <notebook>
      <page string="Internal Notes">
        <field name="comment"/>
      </page>
    </notebook>
  </sheet>
</form>', 16, true, 'primary'),
		('partner.search', 'res.partner', 'search', '<search>
  <field name="name"/>
  <field name="email"/>
  <field name="phone"/>
  <field name="city"/>
</search>', 16, true, 'primary'),

		('invoice.list', 'account.move', 'list', '<list>
  <field name="name"/>
  <field name="partner_id"/>
  <field name="invoice_date"/>
  <field name="date"/>
  <field name="move_type"/>
  <field name="state"/>
  <field name="amount_total"/>
</list>', 16, true, 'primary'),

		('sale.list', 'sale.order', 'list', '<list>
  <field name="name"/>
  <field name="partner_id"/>
  <field name="date_order"/>
  <field name="state"/>
  <field name="amount_total"/>
</list>', 16, true, 'primary'),

		('lead.list', 'crm.lead', 'list', '<list>
  <field name="name"/>
  <field name="partner_name"/>
  <field name="email_from"/>
  <field name="phone"/>
  <field name="stage_id"/>
  <field name="expected_revenue"/>
  <field name="user_id"/>
</list>', 16, true, 'primary'),

		('partner.kanban', 'res.partner', 'kanban', '<kanban>
  <templates>
    <t t-name="card">
      <div class="oe_kanban_global_click">
        <strong><field name="name"/></strong>
        <div><field name="email"/></div>
        <div><field name="phone"/></div>
        <div><field name="city"/></div>
      </div>
    </t>
  </templates>
</kanban>', 16, true, 'primary'),

		('lead.kanban', 'crm.lead', 'kanban', '<kanban default_group_by="stage_id">
  <templates>
    <t t-name="card">
      <div class="oe_kanban_global_click">
        <strong><field name="name"/></strong>
        <div><field name="partner_name"/></div>
        <div>Revenue: <field name="expected_revenue"/></div>
      </div>
    </t>
  </templates>
</kanban>', 16, true, 'primary'),

		('sale.form', 'sale.order', 'form', '<form>
  <header>
    <button name="action_confirm" string="Confirm" type="object" class="btn-primary" invisible="state != ''draft''"/>
    <button name="create_invoices" string="Create Invoice" type="object" class="btn-primary" invisible="state != ''sale''"/>
    <button name="action_cancel" string="Cancel" type="object" invisible="state not in (''draft'',''sale'')"/>
    <button name="action_draft" string="Reset to Draft" type="object" invisible="state != ''cancel''"/>
    <field name="state" widget="statusbar" clickable="1"/>
  </header>
  <sheet>
    <div class="oe_title"><h1><field name="name"/></h1></div>
    <group>
      <group>
        <field name="partner_id"/>
        <field name="date_order"/>
        <field name="company_id"/>
      </group>
      <group>
        <field name="currency_id"/>
        <field name="payment_term_id"/>
        <field name="pricelist_id"/>
      </group>
    </group>
    <notebook>
      <page string="Order Lines">
        <field name="order_line">
          <list editable="bottom">
            <field name="product_id"/>
            <field name="name"/>
            <field name="product_uom_qty"/>
            <field name="price_unit"/>
            <field name="price_subtotal"/>
          </list>
        </field>
      </page>
    </notebook>
    <group>
      <group>
        <field name="amount_untaxed"/>
        <field name="amount_tax"/>
        <field name="amount_total"/>
      </group>
    </group>
  </sheet>
</form>', 16, true, 'primary'),

		('invoice.form', 'account.move', 'form', '<form>
  <header>
    <button name="action_post" string="Post" type="object" class="btn-primary" invisible="state != ''draft''"/>
    <button name="action_register_payment" string="Register Payment" type="object" class="btn-primary" invisible="state != ''posted'' or payment_state == ''paid''"/>
    <button name="button_cancel" string="Cancel" type="object" invisible="state != ''draft''"/>
    <button name="button_draft" string="Reset to Draft" type="object" invisible="state != ''cancel''"/>
    <field name="state" widget="statusbar" clickable="1"/>
  </header>
  <sheet>
    <div class="oe_title"><h1><field name="name"/></h1></div>
    <group>
      <group>
        <field name="partner_id"/>
        <field name="journal_id"/>
        <field name="company_id"/>
        <field name="move_type"/>
      </group>
      <group>
        <field name="date"/>
        <field name="invoice_date"/>
        <field name="invoice_date_due"/>
        <field name="currency_id"/>
        <field name="payment_state"/>
      </group>
    </group>
    <notebook>
      <page string="Invoice Lines">
        <field name="invoice_line_ids">
          <list>
            <field name="name"/>
            <field name="quantity"/>
            <field name="price_unit"/>
            <field name="balance"/>
          </list>
        </field>
      </page>
    </notebook>
    <group>
      <group>
        <field name="amount_untaxed"/>
        <field name="amount_tax"/>
        <field name="amount_total"/>
        <field name="amount_residual"/>
      </group>
    </group>
  </sheet>
</form>', 16, true, 'primary'),

		('purchase.list', 'purchase.order', 'list', '<list>
  <field name="name"/>
  <field name="partner_id"/>
  <field name="date_order"/>
  <field name="state"/>
  <field name="amount_total"/>
</list>', 16, true, 'primary'),

		('employee.list', 'hr.employee', 'list', '<list>
  <field name="name"/>
  <field name="department_id"/>
  <field name="job_id"/>
  <field name="work_email"/>
  <field name="company_id"/>
</list>', 16, true, 'primary'),

		('project.list', 'project.project', 'list', '<list>
  <field name="name"/>
  <field name="partner_id"/>
  <field name="company_id"/>
  <field name="active"/>
</list>', 16, true, 'primary'),

		('lead.form', 'crm.lead', 'form', '<form>
  <header>
    <button name="action_set_won" string="Won" type="object" class="btn-primary" invisible="stage_id != false"/>
    <button name="action_set_lost" string="Lost" type="object" invisible="stage_id != false"/>
    <field name="stage_id" widget="statusbar" options="{''clickable'': ''1''}"/>
  </header>
  <sheet>
    <div class="oe_title"><h1><field name="name" placeholder="Opportunity..."/></h1></div>
    <group>
      <group>
        <field name="partner_id"/>
        <field name="email_from"/>
        <field name="phone"/>
        <field name="user_id"/>
      </group>
      <group>
        <field name="expected_revenue"/>
        <field name="probability"/>
        <field name="date_deadline"/>
        <field name="priority"/>
        <field name="company_id"/>
      </group>
    </group>
    <notebook>
      <page string="Notes">
        <field name="description"/>
      </page>
    </notebook>
  </sheet>
</form>', 10, true, 'primary'),

		('purchase.form', 'purchase.order', 'form', '<form>
  <header>
    <button name="button_confirm" string="Confirm Order" type="object" class="btn-primary" invisible="state != ''draft''"/>
    <field name="state" widget="statusbar" clickable="1"/>
  </header>
  <sheet>
    <div class="oe_title"><h1><field name="name"/></h1></div>
    <group>
      <group>
        <field name="partner_id"/>
        <field name="date_order"/>
        <field name="company_id"/>
      </group>
      <group>
        <field name="currency_id"/>
      </group>
    </group>
    <notebook>
      <page string="Products">
        <field name="order_line">
          <list editable="bottom">
            <field name="product_id"/>
            <field name="name"/>
            <field name="product_qty"/>
            <field name="price_unit"/>
            <field name="price_subtotal"/>
          </list>
        </field>
      </page>
    </notebook>
    <group>
      <group class="oe_subtotal_footer">
        <field name="amount_untaxed"/>
        <field name="amount_tax"/>
        <field name="amount_total" class="oe_subtotal_footer_separator"/>
      </group>
    </group>
  </sheet>
</form>', 10, true, 'primary'),

		('employee.form', 'hr.employee', 'form', '<form>
  <sheet>
    <div class="oe_title"><h1><field name="name" placeholder="Employee Name"/></h1></div>
    <group>
      <group>
        <field name="job_id"/>
        <field name="department_id"/>
        <field name="parent_id"/>
        <field name="coach_id"/>
      </group>
      <group>
        <field name="work_email"/>
        <field name="work_phone"/>
        <field name="mobile_phone"/>
        <field name="company_id"/>
      </group>
    </group>
    <notebook>
      <page string="Personal Information">
        <group>
          <group>
            <field name="gender"/>
            <field name="birthday"/>
            <field name="marital"/>
          </group>
          <group>
            <field name="identification_id"/>
            <field name="country_id"/>
          </group>
        </group>
      </page>
    </notebook>
  </sheet>
</form>', 10, true, 'primary'),

		('project.form', 'project.project', 'form', '<form>
  <sheet>
    <div class="oe_title"><h1><field name="name" placeholder="Project Name"/></h1></div>
    <group>
      <group>
        <field name="partner_id"/>
        <field name="user_id"/>
        <field name="company_id"/>
      </group>
      <group>
        <field name="date_start"/>
        <field name="date"/>
        <field name="active"/>
      </group>
    </group>
    <notebook>
      <page string="Description">
        <field name="description"/>
      </page>
    </notebook>
  </sheet>
</form>', 10, true, 'primary')
	ON CONFLICT DO NOTHING`)

	// Settings form view
	tx.Exec(ctx, `INSERT INTO ir_ui_view (name, model, type, arch, priority, active, mode) VALUES
		('res.config.settings.form', 'res.config.settings', 'form', '<form string="Settings" class="oe_form_configuration">
  <header>
    <button name="execute" string="Save" type="object" class="btn-primary"/>
    <button string="Discard" special="cancel" class="btn-secondary"/>
  </header>
  <div class="o_setting_container">
    <div class="settings">
      <div class="app_settings_block">
        <h2>Company</h2>
        <div class="row mt16 o_settings_container">
          <div class="col-12 col-lg-6 o_setting_box">
            <div class="o_setting_left_pane"/>
            <div class="o_setting_right_pane">
              <label for="company_name"/>
              <div class="text-muted">Your company name</div>
              <field name="company_name"/>
            </div>
          </div>
          <div class="col-12 col-lg-6 o_setting_box">
            <div class="o_setting_left_pane"/>
            <div class="o_setting_right_pane">
              <label for="company_email"/>
              <field name="company_email"/>
            </div>
          </div>
          <div class="col-12 col-lg-6 o_setting_box">
            <div class="o_setting_left_pane"/>
            <div class="o_setting_right_pane">
              <label for="company_phone"/>
              <field name="company_phone"/>
            </div>
          </div>
          <div class="col-12 col-lg-6 o_setting_box">
            <div class="o_setting_left_pane"/>
            <div class="o_setting_right_pane">
              <label for="company_website"/>
              <field name="company_website"/>
            </div>
          </div>
          <div class="col-12 col-lg-6 o_setting_box">
            <div class="o_setting_left_pane"/>
            <div class="o_setting_right_pane">
              <label for="company_vat"/>
              <div class="text-muted">Tax ID / VAT number</div>
              <field name="company_vat"/>
            </div>
          </div>
          <div class="col-12 col-lg-6 o_setting_box">
            <div class="o_setting_left_pane"/>
            <div class="o_setting_right_pane">
              <label for="company_currency_id"/>
              <field name="company_currency_id"/>
            </div>
          </div>
        </div>
        <h2>Address</h2>
        <div class="row mt16 o_settings_container">
          <div class="col-12 col-lg-6 o_setting_box">
            <div class="o_setting_left_pane"/>
            <div class="o_setting_right_pane">
              <label for="company_street"/>
              <field name="company_street"/>
              <field name="company_street2"/>
            </div>
          </div>
          <div class="col-12 col-lg-6 o_setting_box">
            <div class="o_setting_left_pane"/>
            <div class="o_setting_right_pane">
              <label for="company_zip"/>
              <field name="company_zip"/>
              <label for="company_city"/>
              <field name="company_city"/>
            </div>
          </div>
          <div class="col-12 col-lg-6 o_setting_box">
            <div class="o_setting_left_pane"/>
            <div class="o_setting_right_pane">
              <label for="company_country_id"/>
              <field name="company_country_id"/>
            </div>
          </div>
        </div>
        <h2>Features</h2>
        <div class="row mt16 o_settings_container">
          <div class="col-12 col-lg-6 o_setting_box">
            <div class="o_setting_left_pane">
              <field name="group_multi_company"/>
            </div>
            <div class="o_setting_right_pane">
              <label for="group_multi_company"/>
              <div class="text-muted">Manage multiple companies</div>
            </div>
          </div>
          <div class="col-12 col-lg-6 o_setting_box">
            <div class="o_setting_left_pane">
              <field name="module_base_import"/>
            </div>
            <div class="o_setting_right_pane">
              <label for="module_base_import"/>
              <div class="text-muted">Import records from CSV/Excel files</div>
            </div>
          </div>
          <div class="col-12 col-lg-6 o_setting_box">
            <div class="o_setting_left_pane">
              <field name="show_effect"/>
            </div>
            <div class="o_setting_right_pane">
              <label for="show_effect"/>
              <div class="text-muted">Show animation effects</div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
  <field name="company_id" invisible="1"/>
</form>', 10, true, 'primary')
	ON CONFLICT DO NOTHING`)

	// Admin list views for Settings > Technical
	tx.Exec(ctx, `INSERT INTO ir_ui_view (name, model, type, arch, priority, active, mode) VALUES
		('company.list', 'res.company', 'list', '<list>
  <field name="name"/>
  <field name="partner_id"/>
  <field name="currency_id"/>
  <field name="email"/>
  <field name="phone"/>
</list>', 16, true, 'primary'),
		('users.list', 'res.users', 'list', '<list>
  <field name="name"/>
  <field name="login"/>
  <field name="company_id"/>
  <field name="active"/>
</list>', 16, true, 'primary'),
		('config_parameter.list', 'ir.config_parameter', 'list', '<list>
  <field name="key"/>
  <field name="value"/>
</list>', 16, true, 'primary'),
		('ui_view.list', 'ir.ui.view', 'list', '<list>
  <field name="name"/>
  <field name="model"/>
  <field name="type"/>
  <field name="priority"/>
  <field name="active"/>
</list>', 16, true, 'primary'),
		('ui_menu.list', 'ir.ui.menu', 'list', '<list>
  <field name="name"/>
  <field name="parent_id"/>
  <field name="sequence"/>
  <field name="action"/>
</list>', 16, true, 'primary')
	ON CONFLICT DO NOTHING`)

	log.Println("db: UI views seeded")
}

// seedActions inserts action records into ir_act_window and their XML IDs into ir_model_data.
// Mirrors: odoo/addons/base/data/ir_actions_data.xml, contacts/data/..., account/data/..., etc.
func seedActions(ctx context.Context, tx pgx.Tx) {
	log.Println("db: seeding actions...")

	// Action definitions: id, name, res_model, view_mode, domain, context, target, limit, res_id
	type actionDef struct {
		ID       int
		Name     string
		ResModel string
		ViewMode string
		Domain   string
		Context  string
		Target   string
		Limit    int
		ResID    int
		// XML ID parts
		Module string
		XMLName string
	}

	actions := []actionDef{
		{1, "Contacts", "res.partner", "list,kanban,form", "[]", "{}", "current", 80, 0, "contacts", "action_contacts"},
		{2, "Invoices", "account.move", "list,form", `[("move_type","in",["out_invoice","out_refund"])]`, "{}", "current", 80, 0, "account", "action_move_out_invoice_type"},
		{3, "Sale Orders", "sale.order", "list,form", "[]", "{}", "current", 80, 0, "sale", "action_quotations_with_onboarding"},
		{4, "CRM Pipeline", "crm.lead", "kanban,list,form", "[]", "{}", "current", 80, 0, "crm", "crm_lead_all_pipeline"},
		{5, "Transfers", "stock.picking", "list,form", "[]", "{}", "current", 80, 0, "stock", "action_picking_tree_all"},
		{6, "Products", "product.template", "list,form", "[]", "{}", "current", 80, 0, "stock", "action_product_template"},
		{7, "Purchase Orders", "purchase.order", "list,form", "[]", "{}", "current", 80, 0, "purchase", "action_purchase_orders"},
		{8, "Employees", "hr.employee", "list,form", "[]", "{}", "current", 80, 0, "hr", "action_hr_employee"},
		{9, "Departments", "hr.department", "list,form", "[]", "{}", "current", 80, 0, "hr", "action_hr_department"},
		{10, "Projects", "project.project", "list,form", "[]", "{}", "current", 80, 0, "project", "action_project"},
		{11, "Tasks", "project.task", "list,form", "[]", "{}", "current", 80, 0, "project", "action_project_task"},
		{12, "Vehicles", "fleet.vehicle", "list,form", "[]", "{}", "current", 80, 0, "fleet", "action_fleet_vehicle"},
		{100, "Settings", "res.config.settings", "form", "[]", "{}", "current", 80, 1, "base", "action_general_configuration"},
		{104, "Companies", "res.company", "list,form", "[]", "{}", "current", 80, 0, "base", "action_res_company_form"},
		{101, "Users", "res.users", "list,form", "[]", "{}", "current", 80, 0, "base", "action_res_users"},
		{102, "Sequences", "ir.sequence", "list,form", "[]", "{}", "current", 80, 0, "base", "ir_sequence_form"},
		{103, "Change My Preferences", "res.users", "form", "[]", "{}", "new", 80, 0, "base", "action_res_users_my"},
		{105, "Groups", "res.groups", "list,form", "[]", "{}", "current", 80, 0, "base", "action_res_groups"},
		{106, "Logging", "ir.logging", "list,form", "[]", "{}", "current", 80, 0, "base", "action_ir_logging"},
		{107, "System Parameters", "ir.config_parameter", "list,form", "[]", "{}", "current", 80, 0, "base", "action_ir_config_parameter"},
		{108, "Scheduled Actions", "ir.cron", "list,form", "[]", "{}", "current", 80, 0, "base", "action_ir_cron"},
		{109, "Views", "ir.ui.view", "list,form", "[]", "{}", "current", 80, 0, "base", "action_ir_ui_view"},
		{110, "Actions", "ir.actions.act_window", "list,form", "[]", "{}", "current", 80, 0, "base", "action_ir_act_window"},
		{111, "Menus", "ir.ui.menu", "list,form", "[]", "{}", "current", 80, 0, "base", "action_ir_ui_menu"},
		{112, "Access Rights", "ir.model.access", "list,form", "[]", "{}", "current", 80, 0, "base", "action_ir_model_access"},
		{113, "Record Rules", "ir.rule", "list,form", "[]", "{}", "current", 80, 0, "base", "action_ir_rule"},

		// Accounting reports
		{200, "Accounting Reports", "account.report", "list,form", "[]", "{}", "current", 80, 0, "account", "action_account_report"},
		{201, "Vendor Bills", "account.move", "list,form", `[("move_type","in",["in_invoice","in_refund"])]`, `{"default_move_type":"in_invoice"}`, "current", 80, 0, "account", "action_move_in_invoice_type"},
		{202, "Payments", "account.payment", "list,form", "[]", "{}", "current", 80, 0, "account", "action_account_payments"},
		{203, "Bank Statements", "account.bank.statement", "list,form", "[]", "{}", "current", 80, 0, "account", "action_bank_statement_tree"},
		{204, "Chart of Accounts", "account.account", "list,form", "[]", "{}", "current", 80, 0, "account", "action_account_form"},
		{205, "Journals", "account.journal", "list,form", "[]", "{}", "current", 80, 0, "account", "action_account_journal_form"},
		{206, "Taxes", "account.tax", "list,form", "[]", "{}", "current", 80, 0, "account", "action_tax_form"},
		{207, "Analytic Accounts", "account.analytic.account", "list,form", "[]", "{}", "current", 80, 0, "account", "action_analytic_account_form"},
	}

	for _, a := range actions {
		tx.Exec(ctx, `
			INSERT INTO ir_act_window (id, name, type, res_model, view_mode, res_id, domain, context, target, "limit")
			VALUES ($1, $2, 'ir.actions.act_window', $3, $4, $5, $6, $7, $8, $9)
			ON CONFLICT (id) DO NOTHING`,
			a.ID, a.Name, a.ResModel, a.ViewMode, a.ResID, a.Domain, a.Context, a.Target, a.Limit)

		tx.Exec(ctx, `
			INSERT INTO ir_model_data (module, name, model, res_id, noupdate)
			VALUES ($1, $2, 'ir.actions.act_window', $3, true)
			ON CONFLICT DO NOTHING`,
			a.Module, a.XMLName, a.ID)
	}

	log.Printf("db: seeded %d actions with XML IDs", len(actions))
}

// seedMenus creates menu records in ir_ui_menu and their XML IDs in ir_model_data.
// Mirrors: odoo/addons/*/data/*_menu.xml — menus are loaded from XML data files.
//
// The action field stores Odoo reference format: "ir.actions.act_window,<id>"
// pointing to the action IDs seeded by seedActions().
// parent_id is NULL for top-level app menus, or references the parent menu ID.
func seedMenus(ctx context.Context, tx pgx.Tx) {
	log.Println("db: seeding menus...")

	type menuDef struct {
		ID       int
		Name     string
		ParentID *int   // nil = top-level
		Sequence int
		Action   string // "ir.actions.act_window,<id>" or ""
		WebIcon  string // FontAwesome icon for top-level menus
		// XML ID parts for ir_model_data
		Module  string
		XMLName string
	}

	// Helper to create a pointer to int
	p := func(id int) *int { return &id }

	menus := []menuDef{
		// ── Contacts ──────────────────────────────────────────────
		{1, "Contacts", nil, 10, "ir.actions.act_window,1", "fa-address-book,#71639e,#FFFFFF", "contacts", "menu_contacts"},
		{10, "Contacts", p(1), 10, "ir.actions.act_window,1", "", "contacts", "menu_contacts_list"},

		// ── Invoicing ────────────────────────────────────────────
		{2, "Invoicing", nil, 20, "ir.actions.act_window,2", "fa-book,#71639e,#FFFFFF", "account", "menu_finance"},
		{20, "Invoices", p(2), 10, "ir.actions.act_window,2", "", "account", "menu_finance_invoices"},
		{22, "Vendor Bills", p(2), 20, "ir.actions.act_window,201", "", "account", "menu_finance_vendor_bills"},
		{23, "Payments", p(2), 30, "ir.actions.act_window,202", "", "account", "menu_finance_payments"},
		{24, "Bank Statements", p(2), 40, "ir.actions.act_window,203", "", "account", "menu_finance_bank_statements"},

		// Invoicing → Reporting
		{25, "Reporting", p(2), 50, "", "", "account", "menu_finance_reporting"},
		{250, "Accounting Reports", p(25), 10, "ir.actions.act_window,200", "", "account", "menu_finance_reports"},

		// Invoicing → Configuration
		{26, "Configuration", p(2), 90, "", "", "account", "menu_finance_configuration"},
		{260, "Chart of Accounts", p(26), 10, "ir.actions.act_window,204", "", "account", "menu_finance_chart_of_accounts"},
		{261, "Journals", p(26), 20, "ir.actions.act_window,205", "", "account", "menu_finance_journals"},
		{262, "Taxes", p(26), 30, "ir.actions.act_window,206", "", "account", "menu_finance_taxes"},
		{263, "Analytic Accounts", p(26), 40, "ir.actions.act_window,207", "", "account", "menu_finance_analytic_accounts"},

		// ── Sales ────────────────────────────────────────────────
		{3, "Sales", nil, 30, "ir.actions.act_window,3", "fa-bar-chart,#71639e,#FFFFFF", "sale", "menu_sale_root"},
		{30, "Orders", p(3), 10, "ir.actions.act_window,3", "", "sale", "menu_sale_orders"},

		// ── CRM ──────────────────────────────────────────────────
		{4, "CRM", nil, 40, "ir.actions.act_window,4", "fa-star,#71639e,#FFFFFF", "crm", "menu_crm_root"},
		{40, "Pipeline", p(4), 10, "ir.actions.act_window,4", "", "crm", "menu_crm_pipeline"},

		// ── Inventory ────────────────────────────────────────────
		{5, "Inventory", nil, 50, "ir.actions.act_window,5", "fa-cubes,#71639e,#FFFFFF", "stock", "menu_stock_root"},
		{50, "Transfers", p(5), 10, "ir.actions.act_window,5", "", "stock", "menu_stock_transfers"},
		{51, "Products", p(5), 20, "ir.actions.act_window,6", "", "stock", "menu_stock_products"},

		// ── Purchase ─────────────────────────────────────────────
		{6, "Purchase", nil, 60, "ir.actions.act_window,7", "fa-shopping-cart,#71639e,#FFFFFF", "purchase", "menu_purchase_root"},
		{60, "Purchase Orders", p(6), 10, "ir.actions.act_window,7", "", "purchase", "menu_purchase_orders"},

		// ── Employees / HR ───────────────────────────────────────
		{7, "Employees", nil, 70, "ir.actions.act_window,8", "fa-users,#71639e,#FFFFFF", "hr", "menu_hr_root"},
		{70, "Employees", p(7), 10, "ir.actions.act_window,8", "", "hr", "menu_hr_employees"},
		{71, "Departments", p(7), 20, "ir.actions.act_window,9", "", "hr", "menu_hr_departments"},

		// ── Project ──────────────────────────────────────────────
		{8, "Project", nil, 80, "ir.actions.act_window,10", "fa-puzzle-piece,#71639e,#FFFFFF", "project", "menu_project_root"},
		{80, "Projects", p(8), 10, "ir.actions.act_window,10", "", "project", "menu_projects"},
		{81, "Tasks", p(8), 20, "ir.actions.act_window,11", "", "project", "menu_project_tasks"},

		// ── Fleet ────────────────────────────────────────────────
		{9, "Fleet", nil, 90, "ir.actions.act_window,12", "fa-car,#71639e,#FFFFFF", "fleet", "menu_fleet_root"},
		{90, "Vehicles", p(9), 10, "ir.actions.act_window,12", "", "fleet", "menu_fleet_vehicles"},

		// ── Settings ─────────────────────────────────────────────
		{100, "Settings", nil, 100, "ir.actions.act_window,100", "fa-cog,#71639e,#FFFFFF", "base", "menu_administration"},
		{101, "Users & Companies", p(100), 10, "", "", "base", "menu_users"},
		{110, "Users", p(101), 10, "ir.actions.act_window,101", "", "base", "menu_action_res_users"},
		{111, "Companies", p(101), 20, "ir.actions.act_window,104", "", "base", "menu_action_res_company_form"},
		{112, "Groups", p(101), 30, "ir.actions.act_window,105", "", "base", "menu_action_res_groups"},

		{102, "Technical", p(100), 20, "", "", "base", "menu_custom"},

		// Database Structure
		{120, "Database Structure", p(102), 10, "", "", "base", "menu_custom_database_structure"},

		// Sequences & Identifiers
		{122, "Sequences", p(102), 15, "ir.actions.act_window,102", "", "base", "menu_custom_sequences"},

		// Parameters
		{125, "Parameters", p(102), 20, "", "", "base", "menu_custom_parameters"},
		{126, "System Parameters", p(125), 10, "ir.actions.act_window,107", "", "base", "menu_ir_config_parameter"},

		// Scheduled Actions
		{128, "Automation", p(102), 25, "", "", "base", "menu_custom_automation"},
		{129, "Scheduled Actions", p(128), 10, "ir.actions.act_window,108", "", "base", "menu_ir_cron"},

		// User Interface
		{130, "User Interface", p(102), 30, "", "", "base", "menu_custom_user_interface"},
		{131, "Views", p(130), 10, "ir.actions.act_window,109", "", "base", "menu_ir_ui_view"},
		{132, "Actions", p(130), 20, "ir.actions.act_window,110", "", "base", "menu_ir_act_window"},
		{133, "Menus", p(130), 30, "ir.actions.act_window,111", "", "base", "menu_ir_ui_menu"},

		// Security
		{135, "Security", p(102), 40, "", "", "base", "menu_custom_security"},
		{136, "Access Rights", p(135), 10, "ir.actions.act_window,112", "", "base", "menu_ir_model_access"},
		{137, "Record Rules", p(135), 20, "ir.actions.act_window,113", "", "base", "menu_ir_rule"},

		// Logging
		{140, "Logging", p(102), 50, "ir.actions.act_window,106", "", "base", "menu_ir_logging"},
	}

	for _, m := range menus {
		// Insert the menu record
		var actionVal *string
		if m.Action != "" {
			actionVal = &m.Action
		}
		var webIconVal *string
		if m.WebIcon != "" {
			webIconVal = &m.WebIcon
		}

		tx.Exec(ctx, `
			INSERT INTO ir_ui_menu (id, name, parent_id, sequence, action, web_icon, active)
			VALUES ($1, $2, $3, $4, $5, $6, true)
			ON CONFLICT (id) DO NOTHING`,
			m.ID, m.Name, m.ParentID, m.Sequence, actionVal, webIconVal)

		// Insert the XML ID into ir_model_data
		tx.Exec(ctx, `
			INSERT INTO ir_model_data (module, name, model, res_id, noupdate)
			VALUES ($1, $2, 'ir.ui.menu', $3, true)
			ON CONFLICT DO NOTHING`,
			m.Module, m.XMLName, m.ID)
	}

	log.Printf("db: seeded %d menus with XML IDs", len(menus))
}

// seedAccountReports creates default accounting report definitions.
// Mirrors: odoo/addons/account_reports/data/account_report_data.xml
func seedAccountReports(ctx context.Context, tx pgx.Tx) {
	log.Println("db: seeding accounting reports...")

	tx.Exec(ctx, `
		INSERT INTO account_report (id, name, report_type, sequence, active) VALUES
		(1, 'Trial Balance', 'trial_balance', 10, true),
		(2, 'Balance Sheet', 'balance_sheet', 20, true),
		(3, 'Profit and Loss', 'profit_loss', 30, true),
		(4, 'Aged Receivable', 'aged_receivable', 40, true),
		(5, 'Aged Payable', 'aged_payable', 50, true),
		(6, 'General Ledger', 'general_ledger', 60, true)
		ON CONFLICT (id) DO NOTHING`)

	log.Println("db: seeded 6 accounting reports")
}

// seedDemoData creates example records for testing.
func seedDemoData(ctx context.Context, tx pgx.Tx) {
	log.Println("db: loading demo data...")

	// Demo customers
	tx.Exec(ctx, `INSERT INTO res_partner (name, is_company, active, type, email, city, lang) VALUES
		('Müller Bau GmbH', true, true, 'contact', 'info@mueller-bau.de', 'München', 'de_DE'),
		('Schmidt & Söhne KG', true, true, 'contact', 'kontakt@schmidt-soehne.de', 'Hamburg', 'de_DE'),
		('Weber Elektro AG', true, true, 'contact', 'info@weber-elektro.de', 'Frankfurt', 'de_DE'),
		('Fischer Metallbau', true, true, 'contact', 'office@fischer-metall.de', 'Stuttgart', 'de_DE'),
		('Hoffmann IT Services', true, true, 'contact', 'hello@hoffmann-it.de', 'Berlin', 'de_DE')
		ON CONFLICT DO NOTHING`)

	// Demo contacts (employees of customers)
	tx.Exec(ctx, `INSERT INTO res_partner (name, is_company, active, type, email, phone, lang) VALUES
		('Thomas Müller', false, true, 'contact', 'thomas@mueller-bau.de', '+49 89 1234567', 'de_DE'),
		('Anna Schmidt', false, true, 'contact', 'anna@schmidt-soehne.de', '+49 40 9876543', 'de_DE'),
		('Peter Weber', false, true, 'contact', 'peter@weber-elektro.de', '+49 69 5551234', 'de_DE')
		ON CONFLICT DO NOTHING`)

	// Demo sale orders
	tx.Exec(ctx, `INSERT INTO sale_order (name, partner_id, company_id, currency_id, state, date_order, amount_untaxed, amount_total) VALUES
		('AG0001', 3, 1, 1, 'sale', '2026-03-15 10:00:00', 18100, 21539),
		('AG0002', 4, 1, 1, 'draft', '2026-03-20 14:30:00', 6000, 7140),
		('AG0003', 5, 1, 1, 'sale', '2026-03-25 09:15:00', 11700, 13923)
		ON CONFLICT DO NOTHING`)

	// Demo sale order lines
	tx.Exec(ctx, `INSERT INTO sale_order_line (order_id, name, product_uom_qty, price_unit, sequence) VALUES
		((SELECT id FROM sale_order WHERE name='AG0001'), 'Baustelleneinrichtung', 1, 12500, 10),
		((SELECT id FROM sale_order WHERE name='AG0001'), 'Erdarbeiten', 3, 2800, 20),
		((SELECT id FROM sale_order WHERE name='AG0002'), 'Beratung IT-Infrastruktur', 40, 150, 10),
		((SELECT id FROM sale_order WHERE name='AG0003'), 'Elektroinstallation', 1, 8500, 10),
		((SELECT id FROM sale_order WHERE name='AG0003'), 'Material Kabel/Dosen', 1, 3200, 20)
		ON CONFLICT DO NOTHING`)

	// Demo invoices (account.move)
	tx.Exec(ctx, `INSERT INTO account_move (name, move_type, state, date, invoice_date, partner_id, journal_id, company_id, currency_id, amount_total, amount_untaxed) VALUES
		('RE/2026/0001', 'out_invoice', 'posted', '2026-03-10', '2026-03-10', 3, 1, 1, 1, 14875, 12500),
		('RE/2026/0002', 'out_invoice', 'draft', '2026-03-20', '2026-03-20', 4, 1, 1, 1, 7140, 6000),
		('RE/2026/0003', 'out_invoice', 'posted', '2026-03-25', '2026-03-25', 5, 1, 1, 1, 13923, 11700)
		ON CONFLICT DO NOTHING`)

	// CRM pipeline stages
	tx.Exec(ctx, `INSERT INTO crm_stage (id, name, sequence, fold, is_won) VALUES
		(1, 'New', 1, false, false),
		(2, 'Qualified', 2, false, false),
		(3, 'Proposition', 3, false, false),
		(4, 'Won', 4, false, true)
		ON CONFLICT (id) DO NOTHING`)

	// CRM demo leads (partner IDs 3-5 are the first three demo companies seeded above)
	tx.Exec(ctx, `INSERT INTO crm_lead (name, type, stage_id, partner_id, expected_revenue, company_id, currency_id, active, state, priority) VALUES
		('Website Redesign', 'opportunity', 1, 3, 15000, 1, 1, true, 'open', '0'),
		('ERP Implementation', 'opportunity', 2, 4, 45000, 1, 1, true, 'open', '0'),
		('Cloud Migration', 'opportunity', 3, 5, 28000, 1, 1, true, 'open', '0')
		ON CONFLICT DO NOTHING`)

	// Products (templates + variants)
	tx.Exec(ctx, `INSERT INTO product_template (id, name, type, list_price, standard_price, sale_ok, purchase_ok, active) VALUES
		(1, 'Server Hosting', 'service', 50.00, 30.00, true, false, true),
		(2, 'Consulting Hours', 'service', 150.00, 80.00, true, false, true),
		(3, 'Laptop', 'consu', 1200.00, 800.00, true, true, true),
		(4, 'Monitor 27"', 'consu', 450.00, 300.00, true, true, true),
		(5, 'Office Chair', 'consu', 350.00, 200.00, true, true, true)
		ON CONFLICT (id) DO NOTHING`)

	tx.Exec(ctx, `INSERT INTO product_product (id, product_tmpl_id, active, default_code) VALUES
		(1, 1, true, 'SRV-HOST'),
		(2, 2, true, 'SRV-CONS'),
		(3, 3, true, 'HW-LAPTOP'),
		(4, 4, true, 'HW-MON27'),
		(5, 5, true, 'HW-CHAIR')
		ON CONFLICT (id) DO NOTHING`)

	// HR Departments
	tx.Exec(ctx, `INSERT INTO hr_department (id, name, company_id) VALUES
		(1, 'Management', 1),
		(2, 'IT', 1),
		(3, 'Sales', 1)
		ON CONFLICT (id) DO NOTHING`)

	// HR Employees
	tx.Exec(ctx, `INSERT INTO hr_employee (id, name, department_id, company_id, work_email) VALUES
		(1, 'Marc Bauer', 1, 1, 'marc@bauer-bau.de'),
		(2, 'Anna Schmidt', 2, 1, 'anna@bauer-bau.de'),
		(3, 'Peter Weber', 3, 1, 'peter@bauer-bau.de')
		ON CONFLICT (id) DO NOTHING`)

	// Project task stages
	tx.Exec(ctx, `INSERT INTO project_task_type (id, name, sequence, fold) VALUES
		(1, 'New', 1, false),
		(2, 'In Progress', 5, false),
		(3, 'Done', 10, true),
		(4, 'Cancelled', 20, true)
		ON CONFLICT (id) DO NOTHING`)

	// Projects
	tx.Exec(ctx, `INSERT INTO project_project (id, name, partner_id, company_id, active) VALUES
		(1, 'Website Redesign', 5, 1, true),
		(2, 'Office Migration', 3, 1, true)
		ON CONFLICT (id) DO NOTHING`)

	log.Println("db: demo data loaded (8 contacts, 3 sale orders, 3 invoices, 4 CRM stages, 3 CRM leads, 5 products, 3 departments, 3 employees, 2 projects)")
}

// SeedBaseData is the legacy function — redirects to setup with defaults.
// Used when running without the setup wizard (e.g., Docker auto-start).
func SeedBaseData(ctx context.Context, pool *pgxpool.Pool) error {
	if !NeedsSetup(ctx, pool) {
		log.Println("db: base data already exists, skipping seed")
		return nil
	}

	adminHash, _ := tools.HashPassword("admin")
	return SeedWithSetup(ctx, pool, SetupConfig{
		CompanyName:   "My Company",
		Street:        "",
		Zip:           "",
		City:          "",
		CountryCode:   "DE",
		CountryName:   "Germany",
		PhoneCode:     "49",
		Email:         "admin@example.com",
		Chart:         "skr03",
		AdminLogin:    "admin",
		AdminPassword: adminHash,
		DemoData:      false,
	})
}

// seedSystemParams inserts default system parameters into ir_config_parameter.
// Mirrors: odoo/addons/base/data/ir_config_parameter_data.xml
func seedSystemParams(ctx context.Context, tx pgx.Tx) {
	log.Println("db: seeding system parameters...")

	// Ensure unique constraint on key column for ON CONFLICT to work
	tx.Exec(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS ir_config_parameter_key_uniq ON ir_config_parameter (key)`)

	// Generate a random UUID for database.uuid
	dbUUID := generateUUID()

	params := []struct {
		key   string
		value string
	}{
		{"web.base.url", "http://localhost:8069"},
		{"database.uuid", dbUUID},
		{"report.url", "http://localhost:8069"},
		{"base.login_cooldown_after", "10"},
		{"base.login_cooldown_duration", "60"},
	}

	for _, p := range params {
		tx.Exec(ctx,
			`INSERT INTO ir_config_parameter (key, value) VALUES ($1, $2)
			 ON CONFLICT (key) DO NOTHING`, p.key, p.value)
	}

	log.Printf("db: seeded %d system parameters", len(params))
}

// seedLanguages inserts English and German language entries into res_lang.
// Mirrors: odoo/addons/base/data/res_lang_data.xml
func seedLanguages(ctx context.Context, tx pgx.Tx) {
	log.Println("db: seeding languages...")

	// English (US) — default language
	tx.Exec(ctx, `
		INSERT INTO res_lang (name, code, iso_code, url_code, active, direction, date_format, time_format, decimal_point, thousands_sep, week_start, grouping)
		VALUES ('English (US)', 'en_US', 'en', 'en', true, 'ltr', '%%m/%%d/%%Y', '%%H:%%M:%%S', '.', ',', '7', '[3,0]')
		ON CONFLICT DO NOTHING`)

	// German (Germany)
	tx.Exec(ctx, `
		INSERT INTO res_lang (name, code, iso_code, url_code, active, direction, date_format, time_format, decimal_point, thousands_sep, week_start, grouping)
		VALUES ('German / Deutsch', 'de_DE', 'de', 'de', true, 'ltr', '%%d.%%m.%%Y', '%%H:%%M:%%S', ',', '.', '1', '[3,0]')
		ON CONFLICT DO NOTHING`)

	log.Println("db: languages seeded (en_US, de_DE)")
}

// seedTranslations inserts German translations for core UI terms into ir_translation.
// Mirrors: odoo/addons/base/i18n/de.po (partially)
//
// These translations are loaded by the web client via /web/webclient/translations
// and used to display UI elements in German.
func seedTranslations(ctx context.Context, tx pgx.Tx) {
	log.Println("db: seeding German translations...")

	translations := []struct {
		src, value, module string
	}{
		// Navigation & App names
		{"Contacts", "Kontakte", "contacts"},
		{"Invoicing", "Rechnungen", "account"},
		{"Sales", "Verkauf", "sale"},
		{"Purchase", "Einkauf", "purchase"},
		{"Inventory", "Lager", "stock"},
		{"Employees", "Mitarbeiter", "hr"},
		{"Project", "Projekt", "project"},
		{"Settings", "Einstellungen", "base"},
		{"Apps", "Apps", "base"},
		{"Discuss", "Diskussion", "base"},
		{"Calendar", "Kalender", "base"},
		{"Dashboard", "Dashboard", "base"},
		{"Fleet", "Fuhrpark", "fleet"},
		{"CRM", "CRM", "crm"},

		// Common field labels
		{"Name", "Name", "base"},
		{"Email", "E-Mail", "base"},
		{"Phone", "Telefon", "base"},
		{"Mobile", "Mobil", "base"},
		{"Company", "Unternehmen", "base"},
		{"Partner", "Partner", "base"},
		{"Active", "Aktiv", "base"},
		{"Date", "Datum", "base"},
		{"Status", "Status", "base"},
		{"Total", "Gesamt", "base"},
		{"Amount", "Betrag", "account"},
		{"Description", "Beschreibung", "base"},
		{"Reference", "Referenz", "base"},
		{"Notes", "Notizen", "base"},
		{"Tags", "Schlagwörter", "base"},
		{"Type", "Typ", "base"},
		{"Country", "Land", "base"},
		{"City", "Stadt", "base"},
		{"Street", "Straße", "base"},
		{"Zip", "PLZ", "base"},
		{"Website", "Webseite", "base"},
		{"Language", "Sprache", "base"},
		{"Currency", "Währung", "base"},
		{"Sequence", "Reihenfolge", "base"},
		{"Priority", "Priorität", "base"},
		{"Color", "Farbe", "base"},
		{"Image", "Bild", "base"},
		{"Attachment", "Anhang", "base"},
		{"Category", "Kategorie", "base"},
		{"Title", "Titel", "base"},

		// Buttons & Actions
		{"Save", "Speichern", "web"},
		{"Discard", "Verwerfen", "web"},
		{"New", "Neu", "web"},
		{"Edit", "Bearbeiten", "web"},
		{"Delete", "Löschen", "web"},
		{"Archive", "Archivieren", "web"},
		{"Unarchive", "Dearchivieren", "web"},
		{"Duplicate", "Duplizieren", "web"},
		{"Import", "Importieren", "web"},
		{"Export", "Exportieren", "web"},
		{"Print", "Drucken", "web"},
		{"Confirm", "Bestätigen", "web"},
		{"Cancel", "Abbrechen", "web"},
		{"Close", "Schließen", "web"},
		{"Apply", "Anwenden", "web"},
		{"Ok", "Ok", "web"},
		{"Yes", "Ja", "web"},
		{"No", "Nein", "web"},
		{"Send", "Senden", "web"},
		{"Refresh", "Aktualisieren", "web"},
		{"Actions", "Aktionen", "web"},
		{"Action", "Aktion", "web"},
		{"Create", "Erstellen", "web"},

		// Search & Filters
		{"Search...", "Suchen...", "web"},
		{"Filters", "Filter", "web"},
		{"Group By", "Gruppieren nach", "web"},
		{"Favorites", "Favoriten", "web"},
		{"Custom Filter", "Benutzerdefinierter Filter", "web"},

		// Status values
		{"Draft", "Entwurf", "base"},
		{"Posted", "Gebucht", "account"},
		{"Cancelled", "Storniert", "base"},
		{"Confirmed", "Bestätigt", "base"},
		{"Done", "Erledigt", "base"},
		{"In Progress", "In Bearbeitung", "base"},
		{"Waiting", "Wartend", "base"},
		{"Sent", "Gesendet", "base"},
		{"Paid", "Bezahlt", "account"},
		{"Open", "Offen", "base"},
		{"Locked", "Gesperrt", "base"},

		// View types & navigation
		{"List", "Liste", "web"},
		{"Form", "Formular", "web"},
		{"Kanban", "Kanban", "web"},
		{"Graph", "Grafik", "web"},
		{"Pivot", "Pivot", "web"},
		{"Map", "Karte", "web"},
		{"Activity", "Aktivität", "web"},

		// Accounting terms
		{"Invoice", "Rechnung", "account"},
		{"Invoices", "Rechnungen", "account"},
		{"Bill", "Eingangsrechnung", "account"},
		{"Bills", "Eingangsrechnungen", "account"},
		{"Payment", "Zahlung", "account"},
		{"Payments", "Zahlungen", "account"},
		{"Journal", "Journal", "account"},
		{"Journals", "Journale", "account"},
		{"Account", "Konto", "account"},
		{"Tax", "Steuer", "account"},
		{"Taxes", "Steuern", "account"},
		{"Untaxed Amount", "Nettobetrag", "account"},
		{"Tax Amount", "Steuerbetrag", "account"},
		{"Total Amount", "Gesamtbetrag", "account"},
		{"Due Date", "Fälligkeitsdatum", "account"},
		{"Journal Entry", "Buchungssatz", "account"},
		{"Journal Entries", "Buchungssätze", "account"},
		{"Credit Note", "Gutschrift", "account"},

		// Sales terms
		{"Quotation", "Angebot", "sale"},
		{"Quotations", "Angebote", "sale"},
		{"Sales Order", "Verkaufsauftrag", "sale"},
		{"Sales Orders", "Verkaufsaufträge", "sale"},
		{"Customer", "Kunde", "sale"},
		{"Customers", "Kunden", "sale"},
		{"Unit Price", "Stückpreis", "sale"},
		{"Quantity", "Menge", "sale"},
		{"Ordered Quantity", "Bestellte Menge", "sale"},
		{"Delivered Quantity", "Gelieferte Menge", "sale"},

		// Purchase terms
		{"Purchase Order", "Bestellung", "purchase"},
		{"Purchase Orders", "Bestellungen", "purchase"},
		{"Vendor", "Lieferant", "purchase"},
		{"Vendors", "Lieferanten", "purchase"},
		{"Request for Quotation", "Angebotsanfrage", "purchase"},

		// Inventory terms
		{"Product", "Produkt", "stock"},
		{"Products", "Produkte", "stock"},
		{"Warehouse", "Lager", "stock"},
		{"Location", "Lagerort", "stock"},
		{"Delivery", "Lieferung", "stock"},
		{"Receipt", "Wareneingang", "stock"},
		{"Picking", "Kommissionierung", "stock"},
		{"Stock", "Bestand", "stock"},

		// HR terms
		{"Employee", "Mitarbeiter", "hr"},
		{"Department", "Abteilung", "hr"},
		{"Job Position", "Stelle", "hr"},
		{"Contract", "Vertrag", "hr"},

		// Time & date
		{"Today", "Heute", "web"},
		{"Yesterday", "Gestern", "web"},
		{"This Week", "Diese Woche", "web"},
		{"This Month", "Dieser Monat", "web"},
		{"This Year", "Dieses Jahr", "web"},
		{"Last 7 Days", "Letzte 7 Tage", "web"},
		{"Last 30 Days", "Letzte 30 Tage", "web"},
		{"Last 365 Days", "Letzte 365 Tage", "web"},

		// Misc UI
		{"Loading...", "Wird geladen...", "web"},
		{"No records found", "Keine Einträge gefunden", "web"},
		{"Are you sure?", "Sind Sie sicher?", "web"},
		{"Warning", "Warnung", "web"},
		{"Error", "Fehler", "web"},
		{"Success", "Erfolg", "web"},
		{"Information", "Information", "web"},
		{"Powered by", "Betrieben von", "web"},
		{"My Profile", "Mein Profil", "web"},
		{"Log out", "Abmelden", "web"},
		{"Preferences", "Einstellungen", "web"},
		{"Documentation", "Dokumentation", "web"},
		{"Support", "Support", "web"},
		{"Shortcuts", "Tastenkürzel", "web"},
	}

	count := 0
	for _, t := range translations {
		_, err := tx.Exec(ctx,
			`INSERT INTO ir_translation (name, lang, type, src, value, module, state)
			 VALUES ('code', $1, 'code', $2, $3, $4, 'translated')
			 ON CONFLICT DO NOTHING`,
			"de_DE", t.src, t.value, t.module)
		if err == nil {
			count++
		}
	}

	log.Printf("db: seeded %d German translations", count)
}

// generateUUID creates a random UUID v4 string.
func generateUUID() string {
	b := make([]byte, 16)
	rand.Read(b)
	// Set UUID version 4 bits
	b[6] = (b[6] & 0x0f) | 0x40
	b[8] = (b[8] & 0x3f) | 0x80
	return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
		b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}

