create extension if not exists pgcrypto;
create extension if not exists postgis;
create extension if not exists timescaledb;

drop schema if exists eventhub cascade;
create schema if not exists eventhub;

create table if not exists eventhub.event_source (
    id integer generated always as identity primary key,
    tenant_key text not null,
    provider_key text not null,
    source_kind text not null,
    source_key text not null,
    source_instance_key text not null default 'default',
    tenant_provider_setting_key text,
    external_fleet_key text,
    created_at timestamptz not null default now(),
    constraint ux_event_source unique (tenant_key, provider_key, source_kind, source_key, source_instance_key)
);

create table if not exists eventhub.import_run (
    id uuid primary key,
    tenant_key text not null,
    event_source_id integer not null references eventhub.event_source(id),
    mode text not null,
    status text not null,
    refresh_master_data_first boolean not null default true,
    source_group_type text,
    source_group_entity_id text,
    source_group_code text,
    source_group_name text,
    import_scope_type text not null,
    root_source_org_entity_id text,
    root_source_org_code text,
    root_source_org_name text,
    include_children boolean not null default false,
    occurred_from timestamptz,
    occurred_to timestamptz,
    requested_event_families text[] not null default '{}',
    acquisition_strategy text,
    metadata jsonb not null default '{}'::jsonb,
    planned_package_count integer not null default 0,
    started_at timestamptz not null default now(),
    finished_at timestamptz,
    error_message text,
    constraint chk_import_run_occ_time_order check (occurred_from is null or occurred_to is null or occurred_from < occurred_to)
);

create table if not exists eventhub.import_cursor (
    id uuid primary key,
    tenant_key text not null,
    event_source_id integer not null references eventhub.event_source(id),
    scope_hash text not null,
    event_family text not null,
    source_kind text not null,
    cursor_type text not null,
    last_source_package_imported_at timestamptz,
    last_source_package_id text,
    last_source_row_updated_at timestamptz,
    last_occurred_to timestamptz,
    updated_at timestamptz not null default now(),
    constraint ux_import_cursor unique (tenant_key, event_source_id, scope_hash, event_family, source_kind, cursor_type)
);

create table if not exists eventhub.data_package (
    id uuid primary key,
    event_source_id integer not null references eventhub.event_source(id),
    import_run_id uuid references eventhub.import_run(id),
    tenant_key text not null,
    package_key text not null,
    package_type text not null,
    status text not null,
    source_group_type text,
    source_group_entity_id text,
    source_group_code text,
    source_group_name text,
    import_scope_type text,
    root_source_org_entity_id text,
    root_source_org_code text,
    root_source_org_name text,
    include_children boolean not null default false,
    occurred_from timestamptz,
    occurred_to timestamptz,
    event_family text,
    business_date date,
    external_package_id text,
    extraction_code text,
    extraction_source_kind text,
    entity_axis text,
    batch_no integer,
    chunk_from timestamptz,
    chunk_to timestamptz,
    source_package_kind text,
    source_package_id text,
    source_package_entity_id text,
    source_package_period_from timestamptz,
    source_package_period_to timestamptz,
    source_package_imported_at timestamptz,
    received_at timestamptz not null default now(),
    completed_at timestamptz,
    event_count integer not null default 0,
    metadata jsonb not null default '{}'::jsonb,
    error_message text,
    constraint ux_data_package_package_key unique (tenant_key, event_source_id, package_key),
    constraint chk_data_package_occ_time_order check (occurred_from is null or occurred_to is null or occurred_from < occurred_to),
    constraint chk_data_package_chunk_time_order check (chunk_from is null or chunk_to is null or chunk_from < chunk_to)
);

create table if not exists eventhub.source_master_entity (
    id uuid primary key,
    tenant_key text not null,
    event_source_id integer not null references eventhub.event_source(id),
    entity_type text not null,
    source_entity_id text not null,
    source_external_key text,
    display_name text,
    active boolean,
    valid_from timestamptz,
    valid_to timestamptz,
    source_updated_at timestamptz,
    payload jsonb not null default '{}'::jsonb,
    created_at timestamptz not null default now(),
    updated_at timestamptz not null default now(),
    constraint ux_source_master_entity unique (tenant_key, event_source_id, entity_type, source_entity_id),
    constraint chk_source_master_entity_valid_time_order check (valid_from is null or valid_to is null or valid_from <= valid_to)
);

create table if not exists eventhub.source_master_relation (
    id uuid primary key,
    tenant_key text not null,
    event_source_id integer not null references eventhub.event_source(id),
    relation_key text not null,
    relation_type text not null,
    from_entity_type text not null,
    from_source_entity_id text not null,
    to_entity_type text not null,
    to_source_entity_id text not null,
    valid_from timestamptz,
    valid_to timestamptz,
    source_updated_at timestamptz,
    payload jsonb not null default '{}'::jsonb,
    created_at timestamptz not null default now(),
    updated_at timestamptz not null default now(),
    constraint ux_source_master_relation unique (tenant_key, event_source_id, relation_key),
    constraint chk_source_master_relation_valid_time_order check (valid_from is null or valid_to is null or valid_from <= valid_to)
);

create table if not exists eventhub.driver (
    id uuid primary key,
    first_names text,
    last_name text,
    birth_date date,
    source_updated_at timestamptz,
    payload jsonb not null default '{}'::jsonb,
    created_at timestamptz not null default now(),
    updated_at timestamptz not null default now()
);

create table if not exists eventhub.driver_card (
    id uuid primary key,
    driver_id uuid references eventhub.driver(id),
    nation text not null,
    card_number text not null,
    source_updated_at timestamptz,
    payload jsonb not null default '{}'::jsonb,
    created_at timestamptz not null default now(),
    updated_at timestamptz not null default now()
);

create table if not exists eventhub.source_driver_identity (
    id uuid primary key,
    tenant_key text not null,
    event_source_id integer not null references eventhub.event_source(id),
    source_driver_entity_id text not null,
    driver_id uuid not null references eventhub.driver(id) on delete cascade,
    source_updated_at timestamptz,
    payload jsonb not null default '{}'::jsonb,
    created_at timestamptz not null default now(),
    updated_at timestamptz not null default now(),
    constraint ux_source_driver_identity unique (tenant_key, event_source_id, source_driver_entity_id)
);

create table if not exists eventhub.source_driver_card_identity (
    id uuid primary key,
    tenant_key text not null,
    event_source_id integer not null references eventhub.event_source(id),
    source_driver_card_entity_id text not null,
    driver_card_id uuid not null references eventhub.driver_card(id) on delete cascade,
    source_updated_at timestamptz,
    payload jsonb not null default '{}'::jsonb,
    created_at timestamptz not null default now(),
    updated_at timestamptz not null default now(),
    constraint ux_source_driver_card_identity unique (tenant_key, event_source_id, source_driver_card_entity_id)
);

create table if not exists eventhub.vehicle (
    id uuid primary key,
    vin text,
    created_at timestamptz not null default now(),
    updated_at timestamptz not null default now()
);

create table if not exists eventhub.vehicle_registration (
    id uuid primary key,
    nation text not null,
    registration_number text not null,
    source_updated_at timestamptz,
    payload jsonb not null default '{}'::jsonb,
    created_at timestamptz not null default now(),
    updated_at timestamptz not null default now()
);

create table if not exists eventhub.source_vehicle_identity (
    id uuid primary key,
    tenant_key text not null,
    event_source_id integer not null references eventhub.event_source(id),
    source_vehicle_entity_id text not null,
    vehicle_id uuid not null references eventhub.vehicle(id) on delete cascade,
    source_updated_at timestamptz,
    payload jsonb not null default '{}'::jsonb,
    created_at timestamptz not null default now(),
    updated_at timestamptz not null default now(),
    constraint ux_source_vehicle_identity unique (tenant_key, event_source_id, source_vehicle_entity_id)
);

create table if not exists eventhub.source_vehicle_registration_identity (
    id uuid primary key,
    tenant_key text not null,
    event_source_id integer not null references eventhub.event_source(id),
    source_registration_entity_id text not null,
    vehicle_registration_id uuid not null references eventhub.vehicle_registration(id) on delete cascade,
    source_updated_at timestamptz,
    payload jsonb not null default '{}'::jsonb,
    created_at timestamptz not null default now(),
    updated_at timestamptz not null default now(),
    constraint ux_source_vehicle_registration_identity unique (tenant_key, event_source_id, source_registration_entity_id)
);

create table if not exists eventhub.vehicle_registration_assignment (
    id uuid primary key,
    tenant_key text not null,
    event_source_id integer not null references eventhub.event_source(id),
    vehicle_registration_id uuid not null references eventhub.vehicle_registration(id) on delete cascade,
    vehicle_id uuid not null references eventhub.vehicle(id) on delete cascade,
    valid_from timestamptz,
    valid_to timestamptz,
    source_updated_at timestamptz,
    payload jsonb not null default '{}'::jsonb,
    created_at timestamptz not null default now(),
    updated_at timestamptz not null default now(),
    constraint chk_vehicle_registration_assignment_valid_time_order check (valid_from is null or valid_to is null or valid_from <= valid_to)
);

create table if not exists eventhub.event (
    id uuid not null,
    event_source_id integer not null references eventhub.event_source(id),
    data_package_id uuid not null references eventhub.data_package(id),
    external_source_event_id text not null,
    driver_id uuid references eventhub.driver(id),
    driver_card_id uuid references eventhub.driver_card(id),
    vehicle_id uuid references eventhub.vehicle(id),
    vehicle_registration_id uuid references eventhub.vehicle_registration(id),
    source_package_id text,
    source_package_entity_id uuid references eventhub.source_master_entity(id),
    occurred_at timestamptz not null,
    received_partner_at timestamptz,
    received_hub_at timestamptz not null default now(),
    event_domain text not null,
    event_type text not null,
    lifecycle text not null,
    odometer_m bigint,
    position geography(Point, 4326),
    payload jsonb not null default '{}'::jsonb,
    manual_entry boolean not null default false,
    source_record_key_hash text not null,
    event_signature_hash text,
    created_at timestamptz not null default now(),
    constraint pk_event primary key (occurred_at, id),
    constraint chk_event_driver_or_vehicle_ref check (
        driver_id is not null
        or driver_card_id is not null
        or vehicle_id is not null
        or vehicle_registration_id is not null
    )
);

create table if not exists eventhub.event_source_record (
    source_record_key_hash text primary key,
    event_occurred_at timestamptz not null,
    event_id uuid not null,
    created_at timestamptz not null default now()
);

create table if not exists eventhub.event_detail (
    event_occurred_at timestamptz not null,
    event_id uuid not null,
    detail_type text not null,
    attributes jsonb not null default '{}'::jsonb,
    created_at timestamptz not null default now(),
    constraint pk_event_detail primary key (event_occurred_at, event_id, detail_type)
);

select create_hypertable(
    'eventhub.event',
    'occurred_at',
    chunk_time_interval => interval '7 days',
    if_not_exists => true
);

alter table eventhub.event_source_record
    add constraint fk_event_source_record_event foreign key (event_occurred_at, event_id)
        references eventhub.event(occurred_at, id)
        on delete cascade
        deferrable initially deferred;

alter table eventhub.event_detail
    add constraint fk_event_detail_event foreign key (event_occurred_at, event_id)
        references eventhub.event(occurred_at, id)
        on delete cascade;

create index if not exists idx_data_package_source_time
    on eventhub.data_package(tenant_key, event_source_id, received_at desc);

create index if not exists idx_data_package_scope
    on eventhub.data_package(tenant_key, import_scope_type, root_source_org_entity_id, occurred_from, occurred_to);

create index if not exists idx_data_package_extraction
    on eventhub.data_package(tenant_key, event_source_id, import_run_id, event_family, extraction_source_kind, extraction_code, batch_no);

create index if not exists idx_import_run_source_status
    on eventhub.import_run(tenant_key, event_source_id, status, started_at desc);

create index if not exists idx_source_master_entity_type_key
    on eventhub.source_master_entity(tenant_key, event_source_id, entity_type, source_external_key)
    where source_external_key is not null;

create index if not exists idx_source_master_entity_payload_gin
    on eventhub.source_master_entity using gin(payload);

create index if not exists idx_source_master_relation_from
    on eventhub.source_master_relation(tenant_key, event_source_id, from_entity_type, from_source_entity_id, relation_type);

create index if not exists idx_source_master_relation_to
    on eventhub.source_master_relation(tenant_key, event_source_id, to_entity_type, to_source_entity_id, relation_type);

create index if not exists idx_source_master_relation_payload_gin
    on eventhub.source_master_relation using gin(payload);

create index if not exists idx_vehicle_vin
    on eventhub.vehicle(vin)
    where vin is not null;

create index if not exists idx_vehicle_registration_plate
    on eventhub.vehicle_registration(nation, registration_number);

create index if not exists idx_vehicle_registration_assignment_registration_time
    on eventhub.vehicle_registration_assignment(vehicle_registration_id, valid_from desc, valid_to);

create index if not exists idx_vehicle_registration_assignment_vehicle_time
    on eventhub.vehicle_registration_assignment(vehicle_id, valid_from desc, valid_to);

create index if not exists idx_event_source_record_event
    on eventhub.event_source_record(event_occurred_at, event_id);

create index if not exists idx_event_signature
    on eventhub.event(event_signature_hash)
    where event_signature_hash is not null;

create index if not exists idx_event_source_time
    on eventhub.event(event_source_id, occurred_at desc);

create index if not exists idx_event_package_time
    on eventhub.event(data_package_id, occurred_at desc);

create index if not exists idx_event_source_package_id
    on eventhub.event(source_package_id)
    where source_package_id is not null;

create index if not exists idx_event_domain_type_time
    on eventhub.event(event_domain, event_type, occurred_at desc);

create index if not exists idx_driver_card_key
    on eventhub.driver_card(nation, card_number);

create index if not exists idx_driver_card_driver
    on eventhub.driver_card(driver_id)
    where driver_id is not null;

create unique index if not exists ux_driver_card_key
    on eventhub.driver_card(nation, card_number);

create unique index if not exists ux_vehicle_vin
    on eventhub.vehicle(vin)
    where vin is not null;

create unique index if not exists ux_vehicle_registration_plate
    on eventhub.vehicle_registration(nation, registration_number);

create index if not exists idx_source_driver_identity_driver
    on eventhub.source_driver_identity(driver_id);

create index if not exists idx_source_driver_card_identity_card
    on eventhub.source_driver_card_identity(driver_card_id);

create index if not exists idx_source_vehicle_identity_vehicle
    on eventhub.source_vehicle_identity(vehicle_id);

create index if not exists idx_source_vehicle_registration_identity_registration
    on eventhub.source_vehicle_registration_identity(vehicle_registration_id);

create index if not exists idx_event_driver_time
    on eventhub.event(driver_id, occurred_at desc)
    where driver_id is not null;

create index if not exists idx_event_driver_card_time
    on eventhub.event(driver_card_id, occurred_at desc)
    where driver_card_id is not null;

create index if not exists idx_event_vehicle_time
    on eventhub.event(vehicle_id, occurred_at desc)
    where vehicle_id is not null;

create index if not exists idx_event_vehicle_registration_time
    on eventhub.event(vehicle_registration_id, occurred_at desc)
    where vehicle_registration_id is not null;

create index if not exists idx_event_position_gist
    on eventhub.event using gist(position)
    where position is not null;

create index if not exists idx_event_payload_gin
    on eventhub.event using gin(payload);

create index if not exists idx_event_detail_type
    on eventhub.event_detail(detail_type);

create index if not exists idx_event_detail_attributes_gin
    on eventhub.event_detail using gin(attributes);

create index if not exists idx_event_detail_yellowfox_slot
    on eventhub.event_detail(detail_type, (attributes ->> 'slot'), event_occurred_at)
    where detail_type in ('DRIVER_ACTIVITY', 'DRIVER_CARD');

create index if not exists idx_event_detail_yellowfox_eventtype_state
    on eventhub.event_detail(
        (attributes ->> 'yellowFoxEventType'),
        (attributes ->> 'yellowFoxState'),
        event_occurred_at
    )
    where attributes ? 'yellowFoxEventType';

create index if not exists idx_event_detail_yellowfox_ignition
    on eventhub.event_detail(detail_type, (attributes ->> 'ignitionState'), event_occurred_at)
    where attributes ? 'ignitionState';
