BEGIN;

-- Household-owned store brands. The legacy public.stores table remains for
-- historical data and system-admin compatibility, but the household shopping
-- flow should use these records plus store_locations.
CREATE TABLE IF NOT EXISTS household_custom_stores (
  id SERIAL PRIMARY KEY,
  household_id INTEGER NOT NULL REFERENCES households(id) ON DELETE CASCADE,
  name VARCHAR(120) NOT NULL,
  normalized_name VARCHAR(120) NOT NULL,
  created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  UNIQUE(household_id, normalized_name)
);

CREATE INDEX IF NOT EXISTS idx_household_custom_stores_household
  ON household_custom_stores(household_id);

CREATE TABLE IF NOT EXISTS store_locations (
  id SERIAL PRIMARY KEY,
  household_id INTEGER NOT NULL REFERENCES households(id) ON DELETE CASCADE,
  household_store_id INTEGER NOT NULL REFERENCES household_custom_stores(id) ON DELETE CASCADE,
  name VARCHAR(160) NOT NULL,
  normalized_name VARCHAR(160) NOT NULL,
  address TEXT,
  is_default BOOLEAN NOT NULL DEFAULT FALSE,
  map_data JSONB NOT NULL DEFAULT '{}'::jsonb,
  created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  UNIQUE(household_store_id, normalized_name)
);

CREATE INDEX IF NOT EXISTS idx_store_locations_household
  ON store_locations(household_id);

CREATE INDEX IF NOT EXISTS idx_store_locations_store
  ON store_locations(household_store_id);

CREATE UNIQUE INDEX IF NOT EXISTS uq_store_locations_default_per_household
  ON store_locations(household_id)
  WHERE is_default;

CREATE TABLE IF NOT EXISTS store_location_zones (
  id SERIAL PRIMARY KEY,
  household_id INTEGER NOT NULL REFERENCES households(id) ON DELETE CASCADE,
  store_location_id INTEGER NOT NULL REFERENCES store_locations(id) ON DELETE CASCADE,
  name VARCHAR(120) NOT NULL,
  normalized_name VARCHAR(120) NOT NULL,
  sort_order INTEGER NOT NULL DEFAULT 0,
  color VARCHAR(32),
  map_metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
  is_active BOOLEAN NOT NULL DEFAULT TRUE,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  UNIQUE(store_location_id, normalized_name)
);

CREATE INDEX IF NOT EXISTS idx_store_location_zones_location_order
  ON store_location_zones(store_location_id, is_active, sort_order, name);

CREATE TABLE IF NOT EXISTS household_item_images (
  id BIGSERIAL PRIMARY KEY,
  household_id INTEGER NOT NULL REFERENCES households(id) ON DELETE CASCADE,
  store_location_id INTEGER REFERENCES store_locations(id) ON DELETE CASCADE,
  household_store_item_id INTEGER REFERENCES household_store_items(id) ON DELETE CASCADE,
  household_list_id INTEGER REFERENCES household_lists(id) ON DELETE CASCADE,
  image_scope VARCHAR(20) NOT NULL CHECK (image_scope IN ('catalog', 'list')),
  image BYTEA NOT NULL,
  mime_type VARCHAR(50) NOT NULL,
  created_by INTEGER REFERENCES users(id) ON DELETE SET NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_household_item_images_item
  ON household_item_images(household_store_item_id, image_scope, created_at DESC);

CREATE INDEX IF NOT EXISTS idx_household_item_images_location
  ON household_item_images(household_id, store_location_id, created_at DESC);

CREATE TABLE IF NOT EXISTS household_item_events (
  id BIGSERIAL PRIMARY KEY,
  household_id INTEGER NOT NULL REFERENCES households(id) ON DELETE CASCADE,
  store_location_id INTEGER NOT NULL REFERENCES store_locations(id) ON DELETE CASCADE,
  household_store_item_id INTEGER REFERENCES household_store_items(id) ON DELETE SET NULL,
  household_list_id INTEGER REFERENCES household_lists(id) ON DELETE SET NULL,
  actor_user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
  event_type VARCHAR(50) NOT NULL CHECK (
    event_type IN (
      'ITEM_ADDED',
      'ITEM_BOUGHT',
      'ITEM_UNBOUGHT',
      'ITEM_QUANTITY_CHANGED',
      'ITEM_DELETED',
      'ITEM_CLASSIFICATION_CHANGED',
      'ITEM_ZONE_CHANGED'
    )
  ),
  quantity_delta INTEGER,
  quantity_after INTEGER,
  metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_household_item_events_location_time
  ON household_item_events(household_id, store_location_id, created_at DESC);

CREATE INDEX IF NOT EXISTS idx_household_item_events_item_time
  ON household_item_events(household_store_item_id, created_at DESC);

-- Allow new location-scoped rows to be independent of the legacy global
-- stores/items catalog.
ALTER TABLE household_store_items
  ALTER COLUMN store_id DROP NOT NULL;

ALTER TABLE household_lists
  ADD COLUMN IF NOT EXISTS store_location_id INTEGER;

ALTER TABLE household_store_items
  ADD COLUMN IF NOT EXISTS store_location_id INTEGER,
  ADD COLUMN IF NOT EXISTS image_id BIGINT REFERENCES household_item_images(id) ON DELETE SET NULL;

ALTER TABLE household_item_classifications
  ADD COLUMN IF NOT EXISTS store_location_id INTEGER,
  ADD COLUMN IF NOT EXISTS zone_id INTEGER REFERENCES store_location_zones(id) ON DELETE SET NULL;

ALTER TABLE household_list_history
  ADD COLUMN IF NOT EXISTS store_location_id INTEGER;

ALTER TABLE household_lists
  ADD COLUMN IF NOT EXISTS image_id BIGINT REFERENCES household_item_images(id) ON DELETE SET NULL;

-- One owner per household, with defensive cleanup for older data.
WITH ranked_owners AS (
  SELECT
    id,
    ROW_NUMBER() OVER (
      PARTITION BY household_id
      ORDER BY joined_at ASC, id ASC
    ) AS owner_rank
  FROM household_members
  WHERE role = 'owner'
)
UPDATE household_members hm
SET role = 'admin'
FROM ranked_owners ro
WHERE hm.id = ro.id
  AND ro.owner_rank > 1;

CREATE UNIQUE INDEX IF NOT EXISTS uq_household_members_one_owner
  ON household_members(household_id)
  WHERE role = 'owner';

-- Backfill custom stores from current household/global-store links.
INSERT INTO household_custom_stores (
  household_id,
  name,
  normalized_name,
  created_at,
  updated_at
)
SELECT
  hs.household_id,
  s.name,
  LOWER(TRIM(s.name)),
  COALESCE(MIN(hs.added_at), NOW()),
  NOW()
FROM household_stores hs
JOIN stores s ON s.id = hs.store_id
GROUP BY hs.household_id, s.name
ON CONFLICT (household_id, normalized_name) DO NOTHING;

WITH ranked_links AS (
  SELECT
    hs.household_id,
    hcs.id AS household_store_id,
    ROW_NUMBER() OVER (
      PARTITION BY hs.household_id
      ORDER BY hs.is_default DESC, hs.added_at ASC, hs.id ASC
    ) AS household_rank
  FROM household_stores hs
  JOIN stores s ON s.id = hs.store_id
  JOIN household_custom_stores hcs
    ON hcs.household_id = hs.household_id
   AND hcs.normalized_name = LOWER(TRIM(s.name))
)
INSERT INTO store_locations (
  household_id,
  household_store_id,
  name,
  normalized_name,
  is_default,
  created_at,
  updated_at
)
SELECT
  household_id,
  household_store_id,
  'Default Location',
  'default location',
  household_rank = 1,
  NOW(),
  NOW()
FROM ranked_links
ON CONFLICT (household_store_id, normalized_name) DO NOTHING;

-- Backfill location ids onto existing records.
UPDATE household_store_items hsi
SET store_location_id = sl.id
FROM stores s
JOIN household_custom_stores hcs
  ON hcs.normalized_name = LOWER(TRIM(s.name))
JOIN store_locations sl
  ON sl.household_store_id = hcs.id
 AND sl.normalized_name = 'default location'
WHERE hsi.store_id = s.id
  AND hcs.household_id = hsi.household_id
  AND hsi.store_location_id IS NULL;

UPDATE household_lists hl
SET store_location_id = hsi.store_location_id
FROM household_store_items hsi
WHERE hl.household_store_item_id = hsi.id
  AND hl.store_location_id IS NULL;

UPDATE household_item_classifications hic
SET store_location_id = hsi.store_location_id
FROM household_store_items hsi
WHERE hic.household_store_item_id = hsi.id
  AND hic.store_location_id IS NULL;

UPDATE household_list_history hlh
SET store_location_id = hl.store_location_id
FROM household_lists hl
WHERE hlh.household_list_id = hl.id
  AND hlh.store_location_id IS NULL;

DO $$
BEGIN
  IF EXISTS (SELECT 1 FROM household_store_items WHERE store_location_id IS NULL) THEN
    RAISE EXCEPTION 'Failed to backfill household_store_items.store_location_id';
  END IF;

  IF EXISTS (SELECT 1 FROM household_lists WHERE store_location_id IS NULL) THEN
    RAISE EXCEPTION 'Failed to backfill household_lists.store_location_id';
  END IF;

  IF EXISTS (SELECT 1 FROM household_item_classifications WHERE store_location_id IS NULL) THEN
    RAISE EXCEPTION 'Failed to backfill household_item_classifications.store_location_id';
  END IF;
END $$;

ALTER TABLE household_store_items
  ALTER COLUMN store_location_id SET NOT NULL,
  ADD CONSTRAINT household_store_items_store_location_id_fkey
    FOREIGN KEY (store_location_id) REFERENCES store_locations(id) ON DELETE CASCADE;

ALTER TABLE household_lists
  ALTER COLUMN store_location_id SET NOT NULL,
  ADD CONSTRAINT household_lists_store_location_id_fkey
    FOREIGN KEY (store_location_id) REFERENCES store_locations(id) ON DELETE CASCADE;

ALTER TABLE household_item_classifications
  ALTER COLUMN store_location_id SET NOT NULL,
  ADD CONSTRAINT household_item_classifications_store_location_id_fkey
    FOREIGN KEY (store_location_id) REFERENCES store_locations(id) ON DELETE CASCADE;

ALTER TABLE household_list_history
  ADD CONSTRAINT household_list_history_store_location_id_fkey
    FOREIGN KEY (store_location_id) REFERENCES store_locations(id) ON DELETE CASCADE;

-- Seed v1 zones for every migrated/default location.
WITH default_zones(name, sort_order, color) AS (
  VALUES
    ('Entrance & Seasonal', 10, '#64748b'),
    ('Produce & Fresh Vegetables', 20, '#16a34a'),
    ('Meat & Seafood Counter', 30, '#dc2626'),
    ('Deli & Prepared Foods', 40, '#ea580c'),
    ('Bakery', 50, '#ca8a04'),
    ('Dairy & Refrigerated', 60, '#0284c7'),
    ('Frozen Foods', 70, '#2563eb'),
    ('Center Aisles (Dry Goods)', 80, '#7c3aed'),
    ('Beverages & Water', 90, '#0891b2'),
    ('Snacks & Candy', 100, '#db2777'),
    ('Household & Cleaning', 110, '#475569'),
    ('Health & Beauty', 120, '#9333ea'),
    ('Checkout Area', 130, '#0f172a')
)
INSERT INTO store_location_zones (
  household_id,
  store_location_id,
  name,
  normalized_name,
  sort_order,
  color
)
SELECT
  sl.household_id,
  sl.id,
  dz.name,
  LOWER(TRIM(dz.name)),
  dz.sort_order,
  dz.color
FROM store_locations sl
CROSS JOIN default_zones dz
ON CONFLICT (store_location_id, normalized_name) DO NOTHING;

WITH existing_zone_names AS (
  SELECT DISTINCT
    hic.household_id,
    hic.store_location_id,
    TRIM(hic.zone) AS name
  FROM household_item_classifications hic
  WHERE hic.zone IS NOT NULL
    AND TRIM(hic.zone) <> ''
),
ranked_existing_zones AS (
  SELECT
    ezn.*,
    1000 + ROW_NUMBER() OVER (
      PARTITION BY ezn.store_location_id
      ORDER BY ezn.name
    ) AS sort_order
  FROM existing_zone_names ezn
)
INSERT INTO store_location_zones (
  household_id,
  store_location_id,
  name,
  normalized_name,
  sort_order
)
SELECT
  household_id,
  store_location_id,
  name,
  LOWER(TRIM(name)),
  sort_order
FROM ranked_existing_zones
ON CONFLICT (store_location_id, normalized_name) DO NOTHING;

UPDATE household_item_classifications hic
SET zone_id = slz.id
FROM store_location_zones slz
WHERE slz.store_location_id = hic.store_location_id
  AND slz.normalized_name = LOWER(TRIM(hic.zone))
  AND hic.zone_id IS NULL
  AND hic.zone IS NOT NULL
  AND TRIM(hic.zone) <> '';

-- Backfill image references while retaining old BYTEA columns for rollback and
-- old-code compatibility.
INSERT INTO household_item_images (
  household_id,
  store_location_id,
  household_store_item_id,
  image_scope,
  image,
  mime_type,
  created_at
)
SELECT
  hsi.household_id,
  hsi.store_location_id,
  hsi.id,
  'catalog',
  hsi.custom_image,
  COALESCE(hsi.custom_image_mime_type, 'image/jpeg'),
  COALESCE(hsi.updated_at, NOW())
FROM household_store_items hsi
WHERE hsi.custom_image IS NOT NULL
  AND hsi.image_id IS NULL;

UPDATE household_store_items hsi
SET image_id = img.id
FROM household_item_images img
WHERE img.household_store_item_id = hsi.id
  AND img.image_scope = 'catalog'
  AND hsi.image_id IS NULL;

INSERT INTO household_item_images (
  household_id,
  store_location_id,
  household_store_item_id,
  household_list_id,
  image_scope,
  image,
  mime_type,
  created_by,
  created_at
)
SELECT
  hl.household_id,
  hl.store_location_id,
  hl.household_store_item_id,
  hl.id,
  'list',
  hl.custom_image,
  COALESCE(hl.custom_image_mime_type, 'image/jpeg'),
  hl.added_by,
  COALESCE(hl.modified_on, NOW())
FROM household_lists hl
WHERE hl.custom_image IS NOT NULL
  AND hl.image_id IS NULL;

UPDATE household_lists hl
SET image_id = img.id
FROM household_item_images img
WHERE img.household_list_id = hl.id
  AND img.image_scope = 'list'
  AND hl.image_id IS NULL;

-- Backfill known add history into the new event log.
INSERT INTO household_item_events (
  household_id,
  store_location_id,
  household_store_item_id,
  household_list_id,
  actor_user_id,
  event_type,
  quantity_delta,
  metadata,
  created_at
)
SELECT
  hl.household_id,
  hl.store_location_id,
  hlh.household_store_item_id,
  hlh.household_list_id,
  hlh.added_by,
  'ITEM_ADDED',
  hlh.quantity,
  jsonb_build_object('source', 'household_list_history'),
  hlh.added_on
FROM household_list_history hlh
JOIN household_lists hl ON hl.id = hlh.household_list_id
WHERE NOT EXISTS (
  SELECT 1
  FROM household_item_events hie
  WHERE hie.household_list_id = hlh.household_list_id
    AND hie.household_store_item_id = hlh.household_store_item_id
    AND hie.event_type = 'ITEM_ADDED'
    AND hie.created_at = hlh.added_on
);

CREATE UNIQUE INDEX IF NOT EXISTS uq_household_store_items_location_name
  ON household_store_items(household_id, store_location_id, normalized_name);

CREATE UNIQUE INDEX IF NOT EXISTS uq_household_lists_location_item
  ON household_lists(household_id, store_location_id, household_store_item_id);

CREATE UNIQUE INDEX IF NOT EXISTS uq_household_item_classifications_location_item
  ON household_item_classifications(household_id, store_location_id, household_store_item_id);

CREATE INDEX IF NOT EXISTS idx_household_lists_location_bought
  ON household_lists(household_id, store_location_id, bought);

CREATE INDEX IF NOT EXISTS idx_household_classifications_location_zone
  ON household_item_classifications(household_id, store_location_id, zone_id);

CREATE INDEX IF NOT EXISTS idx_household_list_history_location_item
  ON household_list_history(store_location_id, household_store_item_id, added_on DESC);

COMMIT;
