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.nation (
    numeric_code integer primary key,
    alpha_code text,
    name text not null,
    default_language_code text,
    is_known boolean not null default true,
    created_at timestamptz not null default now(),
    updated_at timestamptz not null default now()
);

insert into eventhub.nation(numeric_code, alpha_code, name, default_language_code, is_known)
values
    (0, '0', 'Unknown 0', null, true),
    (1, 'A', 'Austria', 'de-AT', true),
    (2, 'AL', 'Albania', 'sq-AL', true),
    (3, 'AND', 'Andorra', 'ca', true),
    (4, 'ARM', 'Armenia', 'hy-AM', true),
    (5, 'AZ', 'Azerbaijan', 'az', true),
    (6, 'B', 'Belgium', null, true),
    (7, 'BG', 'Bulgaria', 'bg-BG', true),
    (8, 'BIH', 'Bosnia Herzegovina', null, true),
    (9, 'BY', 'Belarus', 'be-BY', true),
    (10, 'CH', 'Switzerland', 'de-CH', true),
    (11, 'CY', 'Cyprus', null, true),
    (12, 'CZ', 'Czech Republic', 'cs-CZ', true),
    (13, 'D', 'Germany', 'de-DE', true),
    (14, 'DK', 'Denmark', 'da-DK', true),
    (15, 'E', 'Spain', 'es-ES', true),
    (16, 'EST', 'Estonia', 'et-EE', true),
    (17, 'F', 'France', 'fr-FR', true),
    (18, 'FIN', 'Finland', 'fi-FI', true),
    (19, 'FL', 'Liechtenstein', 'de-LI', true),
    (20, 'FR', 'Faroe Islands', 'fo-FO', true),
    (21, 'UK', 'United Kingdom', 'en-GB', true),
    (22, 'GE', 'Georgia', 'ka-GE', true),
    (23, 'GR', 'Greece', 'el-GR', true),
    (24, 'H', 'Hungary', 'hu-HU', true),
    (25, 'HR', 'Croatia', 'hr-HR', true),
    (26, 'I', 'Italy', 'it-IT', true),
    (27, 'IRL', 'Ireland', 'en-IE', true),
    (28, 'IS', 'Iceland', 'is-IS', true),
    (29, 'KZ', 'Kazakhstan', 'kk-KZ', true),
    (30, 'L', 'Luxembourg', 'de-LU', true),
    (31, 'LT', 'Lithuania', 'lt-LT', true),
    (32, 'LV', 'Latvia', 'lv-LV', true),
    (33, 'M', 'Malta', null, true),
    (34, 'MC', 'Monaco', 'fr-MC', true),
    (35, 'MD', 'Moldova', 'ro', true),
    (36, 'MK', 'North Macedonia', 'mk-MK', true),
    (37, 'N', 'Norway', 'no', true),
    (38, 'NL', 'Netherlands', 'nl-NL', true),
    (39, 'P', 'Portugal', 'pt-PT', true),
    (40, 'PL', 'Poland', 'pl-PL', true),
    (41, 'RO', 'Romania', 'ro-RO', true),
    (42, 'RSM', 'San Marino', 'it-IT', true),
    (43, 'RUS', 'Russia', 'ru-RU', true),
    (44, 'S', 'Sweden', 'sv-SE', true),
    (45, 'SK', 'Slovakia', 'sk-SK', true),
    (46, 'SLO', 'Slovenia', 'sl-SI', true),
    (47, 'TM', 'Turkmenistan', null, true),
    (48, 'TR', 'Turkey', 'tr-TR', true),
    (49, 'UA', 'Ukraine', 'uk-UA', true),
    (50, 'V', 'Vatican City', 'it-IT', true),
    (51, 'YU', 'Yugoslavia', null, true),
    (52, 'MNE', 'Montenegro', null, true),
    (53, 'SRB', 'Serbia', null, true),
    (54, 'UZ', 'Uzbekistan', null, true),
    (55, 'TJ', 'Tajikistan', null, true),
    (253, 'EC', 'European Community', null, true),
    (254, 'EUR', 'Rest of Europe', null, true),
    (255, 'WLD', 'Rest of the world', null, true)
on conflict (numeric_code) do update set
    alpha_code = excluded.alpha_code,
    name = excluded.name,
    default_language_code = excluded.default_language_code,
    is_known = excluded.is_known,
    updated_at = now();

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,
    nation_numeric_code integer references eventhub.nation(numeric_code),
    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,
    nation_numeric_code integer references eventhub.nation(numeric_code),
    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_numeric_key
    on eventhub.vehicle_registration(nation_numeric_code, registration_number)
    where nation_numeric_code is not null;

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_numeric_key
    on eventhub.driver_card(nation_numeric_code, card_number)
    where nation_numeric_code is not null;

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 unique index if not exists ux_eventhub_nation_alpha_code
    on eventhub.nation(alpha_code)
    where alpha_code is not null;

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';
