-- Network Infrastructure Map — V1 schema.
-- Adds 7 tables (sites, locations, devices, ports, links, vlans, imports)
-- plus a private Storage bucket for uploaded source documents.
-- RLS: read for admin/it_staff/dispatcher/programmer; write for admin/it_staff only.
-- See plan: C:\Users\marcr\.claude\plans\i-m-thinking-of-nested-lagoon.md

-- =============================================================================
-- TABLES
-- =============================================================================

-- Sites: top-level physical locations (campus, building treated standalone)
CREATE TABLE IF NOT EXISTS public.network_sites (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  name text NOT NULL,
  address text,
  notes text,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now(),
  created_by uuid REFERENCES public.profiles(id)
);

-- Locations: nested physical hierarchy within a site
CREATE TABLE IF NOT EXISTS public.network_locations (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  site_id uuid NOT NULL REFERENCES public.network_sites(id) ON DELETE CASCADE,
  parent_id uuid REFERENCES public.network_locations(id) ON DELETE CASCADE,
  name text NOT NULL,
  kind text NOT NULL CHECK (kind IN ('building','floor','room','rack','wall_jack','other')),
  position_label text,
  notes text,
  created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_network_locations_site ON public.network_locations(site_id);
CREATE INDEX IF NOT EXISTS idx_network_locations_parent ON public.network_locations(parent_id);

-- Devices: switches, routers, APs, endpoints, patch panels
CREATE TABLE IF NOT EXISTS public.network_devices (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  name text NOT NULL,
  kind text NOT NULL CHECK (kind IN
    ('router','switch','ap','firewall','server','endpoint','patch_panel','other')),
  role text CHECK (role IN ('core','distribution','access','edge','endpoint')),
  vendor text,
  model text,
  serial text,
  mgmt_ip inet,
  mac macaddr,
  location_id uuid REFERENCES public.network_locations(id) ON DELETE SET NULL,
  import_source text NOT NULL DEFAULT 'manual'
    CHECK (import_source IN ('manual','ai_import','agent')),
  notes text,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_network_devices_location ON public.network_devices(location_id);
CREATE INDEX IF NOT EXISTS idx_network_devices_role ON public.network_devices(role);
CREATE INDEX IF NOT EXISTS idx_network_devices_kind ON public.network_devices(kind);

-- Ports: physical/logical ports on a device
CREATE TABLE IF NOT EXISTS public.network_ports (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  device_id uuid NOT NULL REFERENCES public.network_devices(id) ON DELETE CASCADE,
  port_number text NOT NULL,
  port_kind text CHECK (port_kind IN ('rj45','sfp','sfp_plus','console','power','wireless','virtual','other')),
  speed_mbps integer,
  access_vlan integer,
  is_trunk boolean NOT NULL DEFAULT false,
  trunk_vlans integer[],
  notes text,
  UNIQUE (device_id, port_number)
);
CREATE INDEX IF NOT EXISTS idx_network_ports_device ON public.network_ports(device_id);

-- Links: port-to-port edges. Canonical ordering (port_a < port_b) prevents A↔B/B↔A dupes.
CREATE TABLE IF NOT EXISTS public.network_links (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  port_a uuid NOT NULL REFERENCES public.network_ports(id) ON DELETE CASCADE,
  port_b uuid NOT NULL REFERENCES public.network_ports(id) ON DELETE CASCADE,
  link_kind text CHECK (link_kind IN ('copper','fiber','wireless','virtual','unknown')),
  cable_label text,
  notes text,
  created_at timestamptz NOT NULL DEFAULT now(),
  CHECK (port_a < port_b),
  UNIQUE (port_a, port_b)
);

-- VLANs (org-scoped, named)
CREATE TABLE IF NOT EXISTS public.network_vlans (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  vlan_id integer NOT NULL UNIQUE,
  name text NOT NULL,
  description text,
  color text
);

-- Import sessions: each upload + extraction state + raw AI result for audit/replay
CREATE TABLE IF NOT EXISTS public.network_imports (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  storage_path text NOT NULL,
  file_kind text NOT NULL
    CHECK (file_kind IN ('pdf','image','vsdx','csv','xlsx','docx','other')),
  status text NOT NULL DEFAULT 'pending'
    CHECK (status IN ('pending','extracting','review','applied','discarded','failed')),
  ai_result jsonb,
  error_message text,
  applied_device_ids uuid[],
  applied_link_ids uuid[],
  applied_at timestamptz,
  created_by uuid NOT NULL REFERENCES public.profiles(id),
  created_at timestamptz NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_network_imports_status ON public.network_imports(status);
CREATE INDEX IF NOT EXISTS idx_network_imports_created_by ON public.network_imports(created_by);

-- =============================================================================
-- updated_at TRIGGERS
-- =============================================================================
-- Reuses common pattern from prior Tasq migrations.

CREATE OR REPLACE FUNCTION public.network_map_set_updated_at()
RETURNS trigger AS $$
BEGIN
  NEW.updated_at = now();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS trg_network_sites_updated_at ON public.network_sites;
CREATE TRIGGER trg_network_sites_updated_at
  BEFORE UPDATE ON public.network_sites
  FOR EACH ROW EXECUTE FUNCTION public.network_map_set_updated_at();

DROP TRIGGER IF EXISTS trg_network_devices_updated_at ON public.network_devices;
CREATE TRIGGER trg_network_devices_updated_at
  BEFORE UPDATE ON public.network_devices
  FOR EACH ROW EXECUTE FUNCTION public.network_map_set_updated_at();

-- =============================================================================
-- ROW LEVEL SECURITY
-- =============================================================================
-- Pattern matches supabase/migrations/20260223090000_services_read_only_for_standard_it_dispatcher.sql
-- READ: admin + it_staff + dispatcher + programmer
-- WRITE: admin + it_staff

ALTER TABLE public.network_sites      ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.network_locations  ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.network_devices    ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.network_ports      ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.network_links      ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.network_vlans      ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.network_imports    ENABLE ROW LEVEL SECURITY;

-- ----- network_sites -----
DROP POLICY IF EXISTS "Network sites: select" ON public.network_sites;
CREATE POLICY "Network sites: select" ON public.network_sites
  FOR SELECT USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid()
              AND p.role IN ('admin','it_staff','dispatcher','programmer'))
  );

DROP POLICY IF EXISTS "Network sites: write" ON public.network_sites;
CREATE POLICY "Network sites: write" ON public.network_sites
  FOR ALL USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  ) WITH CHECK (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  );

-- ----- network_locations -----
DROP POLICY IF EXISTS "Network locations: select" ON public.network_locations;
CREATE POLICY "Network locations: select" ON public.network_locations
  FOR SELECT USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid()
              AND p.role IN ('admin','it_staff','dispatcher','programmer'))
  );

DROP POLICY IF EXISTS "Network locations: write" ON public.network_locations;
CREATE POLICY "Network locations: write" ON public.network_locations
  FOR ALL USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  ) WITH CHECK (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  );

-- ----- network_devices -----
DROP POLICY IF EXISTS "Network devices: select" ON public.network_devices;
CREATE POLICY "Network devices: select" ON public.network_devices
  FOR SELECT USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid()
              AND p.role IN ('admin','it_staff','dispatcher','programmer'))
  );

DROP POLICY IF EXISTS "Network devices: write" ON public.network_devices;
CREATE POLICY "Network devices: write" ON public.network_devices
  FOR ALL USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  ) WITH CHECK (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  );

-- ----- network_ports -----
DROP POLICY IF EXISTS "Network ports: select" ON public.network_ports;
CREATE POLICY "Network ports: select" ON public.network_ports
  FOR SELECT USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid()
              AND p.role IN ('admin','it_staff','dispatcher','programmer'))
  );

DROP POLICY IF EXISTS "Network ports: write" ON public.network_ports;
CREATE POLICY "Network ports: write" ON public.network_ports
  FOR ALL USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  ) WITH CHECK (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  );

-- ----- network_links -----
DROP POLICY IF EXISTS "Network links: select" ON public.network_links;
CREATE POLICY "Network links: select" ON public.network_links
  FOR SELECT USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid()
              AND p.role IN ('admin','it_staff','dispatcher','programmer'))
  );

DROP POLICY IF EXISTS "Network links: write" ON public.network_links;
CREATE POLICY "Network links: write" ON public.network_links
  FOR ALL USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  ) WITH CHECK (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  );

-- ----- network_vlans -----
DROP POLICY IF EXISTS "Network vlans: select" ON public.network_vlans;
CREATE POLICY "Network vlans: select" ON public.network_vlans
  FOR SELECT USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid()
              AND p.role IN ('admin','it_staff','dispatcher','programmer'))
  );

DROP POLICY IF EXISTS "Network vlans: write" ON public.network_vlans;
CREATE POLICY "Network vlans: write" ON public.network_vlans
  FOR ALL USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  ) WITH CHECK (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  );

-- ----- network_imports -----
DROP POLICY IF EXISTS "Network imports: select" ON public.network_imports;
CREATE POLICY "Network imports: select" ON public.network_imports
  FOR SELECT USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid()
              AND p.role IN ('admin','it_staff','dispatcher','programmer'))
  );

DROP POLICY IF EXISTS "Network imports: write" ON public.network_imports;
CREATE POLICY "Network imports: write" ON public.network_imports
  FOR ALL USING (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  ) WITH CHECK (
    EXISTS (SELECT 1 FROM public.profiles p
            WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  );

-- =============================================================================
-- STORAGE BUCKET (private) for uploaded source documents
-- =============================================================================
-- The Edge Function `extract_topology` uses the service role to read from this
-- bucket. Authenticated admin/it_staff clients upload via signed URLs or with
-- their RLS-bound JWT.

INSERT INTO storage.buckets (id, name, public)
VALUES ('network-imports', 'network-imports', false)
ON CONFLICT (id) DO NOTHING;

-- Storage policies: only admin/it_staff can upload; same group + dispatcher/programmer can read.
DROP POLICY IF EXISTS "Network imports bucket: select" ON storage.objects;
CREATE POLICY "Network imports bucket: select" ON storage.objects
  FOR SELECT USING (
    bucket_id = 'network-imports'
    AND EXISTS (SELECT 1 FROM public.profiles p
                WHERE p.id = auth.uid()
                  AND p.role IN ('admin','it_staff','dispatcher','programmer'))
  );

DROP POLICY IF EXISTS "Network imports bucket: write" ON storage.objects;
CREATE POLICY "Network imports bucket: write" ON storage.objects
  FOR INSERT WITH CHECK (
    bucket_id = 'network-imports'
    AND EXISTS (SELECT 1 FROM public.profiles p
                WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  );

DROP POLICY IF EXISTS "Network imports bucket: update" ON storage.objects;
CREATE POLICY "Network imports bucket: update" ON storage.objects
  FOR UPDATE USING (
    bucket_id = 'network-imports'
    AND EXISTS (SELECT 1 FROM public.profiles p
                WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  );

DROP POLICY IF EXISTS "Network imports bucket: delete" ON storage.objects;
CREATE POLICY "Network imports bucket: delete" ON storage.objects
  FOR DELETE USING (
    bucket_id = 'network-imports'
    AND EXISTS (SELECT 1 FROM public.profiles p
                WHERE p.id = auth.uid() AND p.role IN ('admin','it_staff'))
  );
