package services

// FirmDashboardDefaultService manages paliad.firm_dashboard_default — the
// optional firm-wide dashboard layout that DashboardLayoutService prefers
// over the code-resident FactoryDefaultLayout when seeding a new user's
// row or resetting an existing layout.
//
// Design: docs/design-dashboard-configurable-2026-05-20.md §8.2
// (firm-wide default, deferred to v1.1 — activated in Slice C).
//
// Single optional row identified by id=1. Get returns (spec, true, nil)
// when set, (zero, false, nil) when never set. Set overwrites; Clear
// deletes (so GetOrSeed reverts to FactoryDefaultLayout).
//
// The HTTP layer (handlers/firm_dashboard_default.go) enforces admin-only
// via auth.RequireAdmin. The service itself takes no admin parameter —
// it trusts its callers because the only writer is the admin handler;
// the read path is used by DashboardLayoutService on every seed/reset.

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

	"github.com/google/uuid"
	"github.com/jmoiron/sqlx"
)

// FirmDashboardDefaultService manages paliad.firm_dashboard_default.
type FirmDashboardDefaultService struct {
	db *sqlx.DB
}

// NewFirmDashboardDefaultService wires the service.
func NewFirmDashboardDefaultService(db *sqlx.DB) *FirmDashboardDefaultService {
	return &FirmDashboardDefaultService{db: db}
}

// Get returns (spec, true, nil) if a firm default is set, (zero, false,
// nil) otherwise. SanitizeForRead is applied so callers always receive a
// version-coherent spec; if anything had to be dropped (e.g. an admin
// stashed a layout that references widgets we later removed), the
// cleanup is in-memory only and the next admin write will persist it.
func (s *FirmDashboardDefaultService) Get(ctx context.Context) (DashboardLayoutSpec, bool, error) {
	var raw json.RawMessage
	err := s.db.GetContext(ctx, &raw, `
		SELECT layout_json
		  FROM paliad.firm_dashboard_default
		 WHERE id = 1
	`)
	if errors.Is(err, sql.ErrNoRows) {
		return DashboardLayoutSpec{}, false, nil
	}
	if err != nil {
		return DashboardLayoutSpec{}, false, fmt.Errorf("get firm dashboard default: %w", err)
	}
	var spec DashboardLayoutSpec
	if err := json.Unmarshal(raw, &spec); err != nil {
		// Stored row is unparseable — treat as missing so the seed path
		// reverts to FactoryDefaultLayout rather than crash.
		return DashboardLayoutSpec{}, false, nil
	}
	spec.SanitizeForRead()
	return spec, true, nil
}

// Set persists the layout as the firm-wide default. Validates against the
// catalog so an admin can't seed a layout that violates the contract.
// updatedBy is recorded for audit; passing uuid.Nil clears the column.
func (s *FirmDashboardDefaultService) Set(ctx context.Context, spec DashboardLayoutSpec, updatedBy uuid.UUID) (DashboardLayoutSpec, error) {
	if err := spec.Validate(); err != nil {
		return DashboardLayoutSpec{}, err
	}
	bytes, err := json.Marshal(spec)
	if err != nil {
		return DashboardLayoutSpec{}, fmt.Errorf("firm dashboard default marshal: %w", err)
	}
	var updaterArg interface{}
	if updatedBy != uuid.Nil {
		updaterArg = updatedBy
	}
	_, err = s.db.ExecContext(ctx, `
		INSERT INTO paliad.firm_dashboard_default (id, layout_json, updated_by, updated_at)
		VALUES (1, $1, $2, now())
		ON CONFLICT (id) DO UPDATE
		   SET layout_json = EXCLUDED.layout_json,
		       updated_by  = EXCLUDED.updated_by,
		       updated_at  = now()
	`, json.RawMessage(bytes), updaterArg)
	if err != nil {
		return DashboardLayoutSpec{}, fmt.Errorf("firm dashboard default upsert: %w", err)
	}
	return spec, nil
}

// Clear deletes the firm default so seeds/resets revert to FactoryDefault-
// Layout. Idempotent — clearing an already-absent row is a no-op.
func (s *FirmDashboardDefaultService) Clear(ctx context.Context) error {
	_, err := s.db.ExecContext(ctx, `DELETE FROM paliad.firm_dashboard_default WHERE id = 1`)
	if err != nil {
		return fmt.Errorf("firm dashboard default clear: %w", err)
	}
	return nil
}
