-- v4.0_multi_branch.sql
-- Multi-branch overhaul: branch fiscal data + shared inventory with per-branch stock.
-- WARNING: this migration restructures inventory data. A full DB backup is required.

-- ═════════════════════════════════════════════════════════════════════════════
-- 1. BRANCHES: fiscal fields + main flag
-- ═════════════════════════════════════════════════════════════════════════════

ALTER TABLE branches
    ADD COLUMN IF NOT EXISTS is_main BOOLEAN DEFAULT FALSE,
    ADD COLUMN IF NOT EXISTS rfc VARCHAR(13),
    ADD COLUMN IF NOT EXISTS razon_social VARCHAR(300),
    ADD COLUMN IF NOT EXISTS regimen_fiscal VARCHAR(10),
    ADD COLUMN IF NOT EXISTS cp VARCHAR(5),
    ADD COLUMN IF NOT EXISTS direccion_fiscal TEXT,
    ADD COLUMN IF NOT EXISTS serie_cfdi VARCHAR(10) DEFAULT 'A',
    ADD COLUMN IF NOT EXISTS folio_inicio INTEGER DEFAULT 1,
    ADD COLUMN IF NOT EXISTS folio_actual INTEGER DEFAULT 1,
    ADD COLUMN IF NOT EXISTS email VARCHAR(200);

-- Ensure at least one branch is marked main (the first one created).
DO $$
DECLARE
    main_branch_id INTEGER;
    branch_count INTEGER;
BEGIN
    SELECT COUNT(*) INTO branch_count FROM branches;

    IF branch_count = 0 THEN
        INSERT INTO branches (name, is_main)
        VALUES ('Principal', TRUE);
    ELSE
        SELECT id INTO main_branch_id FROM branches ORDER BY id LIMIT 1;

        UPDATE branches SET is_main = FALSE;
        UPDATE branches SET is_main = TRUE WHERE id = main_branch_id;
    END IF;
END $$;

-- Constraint: only one main branch per tenant.
-- Because this runs inside a single tenant DB, a simple partial unique index is enough.
CREATE UNIQUE INDEX IF NOT EXISTS idx_branches_single_main
    ON branches (is_main)
    WHERE is_main = TRUE;

-- ═════════════════════════════════════════════════════════════════════════════
-- 2. INVENTORY STOCK: new per-branch stock table
-- ═════════════════════════════════════════════════════════════════════════════

CREATE TABLE IF NOT EXISTS inventory_stock (
    id              SERIAL PRIMARY KEY,
    inventory_id    INTEGER NOT NULL REFERENCES inventory(id) ON DELETE CASCADE,
    branch_id       INTEGER NOT NULL REFERENCES branches(id) ON DELETE CASCADE,
    stock           INTEGER DEFAULT 0,
    location        VARCHAR(50),
    created_at      TIMESTAMPTZ DEFAULT NOW(),
    updated_at      TIMESTAMPTZ DEFAULT NOW(),
    UNIQUE(inventory_id, branch_id)
);

CREATE INDEX IF NOT EXISTS idx_inventory_stock_branch ON inventory_stock(branch_id);
CREATE INDEX IF NOT EXISTS idx_inventory_stock_inventory ON inventory_stock(inventory_id);

CREATE OR REPLACE FUNCTION update_inventory_stock_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS trg_inventory_stock_updated_at ON inventory_stock;
CREATE TRIGGER trg_inventory_stock_updated_at
    BEFORE UPDATE ON inventory_stock
    FOR EACH ROW
    EXECUTE FUNCTION update_inventory_stock_updated_at();

-- ═════════════════════════════════════════════════════════════════════════════
-- 3. INVENTORY: make branch_id nullable + prepare for consolidation
-- ═════════════════════════════════════════════════════════════════════════════

-- Drop the old unique constraint that forces one record per (branch, part_number).
DROP INDEX IF EXISTS idx_inventory_branch_part;

-- Make branch_id nullable so we can have master records without a branch.
ALTER TABLE inventory ALTER COLUMN branch_id DROP NOT NULL;

-- Add unique constraint on part_number at tenant level so a product exists once.
-- If duplicates still exist this will fail, so we consolidate below first.
-- We create it at the end of this migration after deduplication.

-- ═════════════════════════════════════════════════════════════════════════════
-- 4. DATA MIGRATION: consolidate duplicated inventory rows by part_number
-- ═════════════════════════════════════════════════════════════════════════════

-- Build a mapping: for each duplicated part_number, choose the master record.
-- Master = record belonging to the main branch; fallback = oldest id.
CREATE TEMP TABLE _inventory_master_map AS
SELECT DISTINCT ON (part_number)
    id AS master_id,
    part_number
FROM inventory
ORDER BY part_number,
    CASE WHEN branch_id = (SELECT id FROM branches WHERE is_main = TRUE LIMIT 1) THEN 0 ELSE 1 END,
    id ASC;

-- Create temp table of duplicates (all rows that are NOT the master for their part_number).
CREATE TEMP TABLE _inventory_duplicates AS
SELECT i.id AS duplicate_id, m.master_id
FROM inventory i
JOIN _inventory_master_map m ON i.part_number = m.part_number
WHERE i.id <> m.master_id;



-- Compute per-duplicate stock and insert into inventory_stock against master_id + duplicate's branch.
INSERT INTO inventory_stock (inventory_id, branch_id, stock, location)
SELECT
    dups.master_id,
    dups.branch_id,
    GREATEST(0, COALESCE(stock_by_dup.stock, 0))::int,
    dups.location
FROM (
    SELECT d.master_id, d.duplicate_id, i.branch_id, i.location
    FROM _inventory_duplicates d
    JOIN inventory i ON i.id = d.duplicate_id
) dups
JOIN LATERAL (
    SELECT COALESCE(SUM(quantity), 0) AS stock
    FROM inventory_operations
    WHERE inventory_id = dups.duplicate_id AND branch_id = dups.branch_id
) stock_by_dup ON TRUE
ON CONFLICT (inventory_id, branch_id) DO UPDATE
SET stock = inventory_stock.stock + EXCLUDED.stock;

-- Also migrate stock from master records themselves (they were already in inventory.branch_id).
INSERT INTO inventory_stock (inventory_id, branch_id, stock, location)
SELECT
    i.id,
    i.branch_id,
    GREATEST(0, COALESCE(stock_by_inv.stock, 0))::int,
    i.location
FROM inventory i
JOIN _inventory_master_map m ON i.id = m.master_id
JOIN LATERAL (
    SELECT COALESCE(SUM(quantity), 0) AS stock
    FROM inventory_operations
    WHERE inventory_id = i.id AND branch_id = i.branch_id
) stock_by_inv ON TRUE
WHERE i.branch_id IS NOT NULL
ON CONFLICT (inventory_id, branch_id) DO UPDATE
SET stock = EXCLUDED.stock;

-- Handle inventory_stock_summary specially: it has PK on inventory_id.
-- If master already has a summary row, add duplicate's stock and remove duplicate row.
-- Otherwise repoint the duplicate row to master.
UPDATE inventory_stock_summary s
SET stock = s.stock + d.stock
FROM (
    SELECT duplicate_id, master_id, stock FROM inventory_stock_summary ss
    JOIN _inventory_duplicates m ON ss.inventory_id = m.duplicate_id
    WHERE EXISTS (SELECT 1 FROM inventory_stock_summary sm WHERE sm.inventory_id = m.master_id)
) d
WHERE s.inventory_id = d.master_id;

DELETE FROM inventory_stock_summary
WHERE inventory_id IN (
    SELECT m.duplicate_id
    FROM _inventory_duplicates m
    WHERE EXISTS (SELECT 1 FROM inventory_stock_summary sm WHERE sm.inventory_id = m.master_id)
);

UPDATE inventory_stock_summary
SET inventory_id = m.master_id
FROM _inventory_duplicates m
WHERE inventory_id = m.duplicate_id;

-- Update FK references from duplicate inventory rows to master inventory rows.
-- We use dynamic SQL to update every known referencing table.
DO $$
DECLARE
    rec RECORD;
    fk_sql TEXT;
BEGIN
    FOR rec IN
        SELECT
            tc.table_name,
            kcu.column_name
        FROM information_schema.table_constraints tc
        JOIN information_schema.key_column_usage kcu
            ON tc.constraint_name = kcu.constraint_name
        JOIN information_schema.constraint_column_usage ccu
            ON ccu.constraint_name = tc.constraint_name
        WHERE tc.constraint_type = 'FOREIGN KEY'
          AND ccu.table_name = 'inventory'
          AND tc.table_name <> 'inventory_stock_summary'
    LOOP
        fk_sql := format(
            'UPDATE %I SET %I = m.master_id FROM _inventory_duplicates m WHERE %I = m.duplicate_id',
            rec.table_name, rec.column_name, rec.column_name
        );
        EXECUTE fk_sql;
    END LOOP;
END $$;

-- Delete duplicate inventory rows now that FKs are repointed.
DELETE FROM inventory
WHERE id IN (SELECT duplicate_id FROM _inventory_duplicates);

-- Clean up master records: remove branch_id so they become shared catalog items.
UPDATE inventory SET branch_id = NULL WHERE branch_id IS NOT NULL;

-- Now safe to enforce uniqueness at tenant level.
CREATE UNIQUE INDEX IF NOT EXISTS idx_inventory_part_unique ON inventory (part_number);

-- Clean temp tables.
DROP TABLE IF EXISTS _inventory_master_map;
DROP TABLE IF EXISTS _inventory_duplicates;

-- ═════════════════════════════════════════════════════════════════════════════
-- 5. CFDI_QUEUE: allow sale_id to be NULL for global invoices (Phase 3 prep)
-- ═════════════════════════════════════════════════════════════════════════════

ALTER TABLE cfdi_queue ALTER COLUMN sale_id DROP NOT NULL;

-- ═════════════════════════════════════════════════════════════════════════════
-- 6. TRIGGER: Keep inventory_stock in sync with inventory_operations
-- ═════════════════════════════════════════════════════════════════════════════

CREATE OR REPLACE FUNCTION update_inventory_stock()
RETURNS TRIGGER AS $$
BEGIN
    -- Skip operations that are not tied to a specific branch.
    -- Per-branch stock tracking requires a branch_id; without it we can't
    -- assign the stock to any location.
    IF NEW.branch_id IS NULL THEN
        RETURN NEW;
    END IF;

    INSERT INTO inventory_stock (inventory_id, branch_id, stock)
    VALUES (NEW.inventory_id, NEW.branch_id, NEW.quantity)
    ON CONFLICT (inventory_id, branch_id) DO UPDATE
    SET stock = inventory_stock.stock + EXCLUDED.stock,
        updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS trg_update_inventory_stock ON inventory_operations;
CREATE TRIGGER trg_update_inventory_stock
    AFTER INSERT ON inventory_operations
    FOR EACH ROW
    EXECUTE FUNCTION update_inventory_stock();
