CREATE EXTENSION IF NOT EXISTS postgis;

CREATE TABLE IF NOT EXISTS requests (
    id UUID PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    geometry TEXT NOT NULL,
    remaining_geometry TEXT NOT NULL,
    geometry_area DOUBLE PRECISION NOT NULL,
    remaining_area DOUBLE PRECISION NOT NULL,
    coverage_required_percent DOUBLE PRECISION NOT NULL DEFAULT 100.0,
    importance DOUBLE PRECISION NOT NULL,
    begin_date_time TIMESTAMP NOT NULL,
    end_date_time TIMESTAMP NOT NULL,
    high_priority_transmit BOOLEAN NOT NULL DEFAULT false,
    survey_type VARCHAR(32) NOT NULL,
    status VARCHAR(32) NOT NULL,
    match_count INTEGER NOT NULL DEFAULT 0,
    last_matched_at TIMESTAMP NULL,
    completed_at TIMESTAMP NULL,
    created_at TIMESTAMP NOT NULL,
    updated_at TIMESTAMP NOT NULL,
    deleted_at TIMESTAMP NULL,
    version BIGINT NOT NULL DEFAULT 0,
    CONSTRAINT chk_requests_status
        CHECK (status IN ('ACCEPTED', 'ACTIVE', 'COMPLETED', 'EXPIRED', 'DELETED')),
    CONSTRAINT chk_requests_survey_type
        CHECK (survey_type IN ('OPTICS', 'RSA', 'COMBINED')),
    CONSTRAINT chk_requests_time_window
        CHECK (begin_date_time < end_date_time),
    CONSTRAINT chk_requests_importance_non_negative
        CHECK (importance >= 0),
    CONSTRAINT chk_requests_name_not_blank
        CHECK (length(trim(name)) > 0),
    CONSTRAINT chk_requests_soft_delete
        CHECK ((status = 'DELETED' AND deleted_at IS NOT NULL) OR (status <> 'DELETED'))
);

CREATE TABLE IF NOT EXISTS request_kpp (
    request_id UUID NOT NULL,
    kpp INTEGER NOT NULL,
    CONSTRAINT fk_request_kpp_request
        FOREIGN KEY (request_id)
        REFERENCES requests(id)
        ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS request_optics_params (
    request_id UUID PRIMARY KEY,
    result_type VARCHAR(32) NOT NULL,
    resolution DOUBLE PRECISION NOT NULL,
    sun_angle_min DOUBLE PRECISION NOT NULL DEFAULT 10.0,
    sun_angle_max DOUBLE PRECISION NOT NULL DEFAULT 90.0,
    clouds DOUBLE PRECISION NOT NULL DEFAULT 100.0,
    CONSTRAINT fk_request_optics_params_request
        FOREIGN KEY (request_id)
        REFERENCES requests(id)
        ON DELETE CASCADE,
    CONSTRAINT chk_request_optics_result_type
        CHECK (result_type IN ('PANCHROMATIC', 'MULTISPECTRAL', 'PANSHARPENING'))
);

CREATE TABLE IF NOT EXISTS request_rsa_params (
    request_id UUID PRIMARY KEY,
    result_type VARCHAR(32) NOT NULL,
    interferometry BOOLEAN NOT NULL DEFAULT false,
    polarisation VARCHAR(8) NOT NULL,
    resolution DOUBLE PRECISION NOT NULL,
    CONSTRAINT fk_request_rsa_params_request
        FOREIGN KEY (request_id)
        REFERENCES requests(id)
        ON DELETE CASCADE,
    CONSTRAINT chk_request_rsa_result_type
        CHECK (result_type IN ('SPOTLIGHT', 'STRIPMAP', 'SCANSAR')),
    CONSTRAINT chk_request_rsa_polarisation
        CHECK (polarisation IN ('HH', 'VV'))
);

CREATE TABLE IF NOT EXISTS request_route_matches (
    id BIGSERIAL PRIMARY KEY,
    request_id UUID NOT NULL,
    route_id UUID NOT NULL,
    original_intersection_geometry TEXT NOT NULL,
    original_intersection_area DOUBLE PRECISION NOT NULL,
    applied_intersection_geometry TEXT NULL,
    applied_intersection_area DOUBLE PRECISION NOT NULL DEFAULT 0.0,
    coverage_delta_percent DOUBLE PRECISION NOT NULL DEFAULT 0.0,
    contributes_to_coverage BOOLEAN NOT NULL DEFAULT false,
    matched_at TIMESTAMP NOT NULL,
    CONSTRAINT fk_request_route_matches_request
        FOREIGN KEY (request_id)
        REFERENCES requests(id)
        ON DELETE CASCADE,
    CONSTRAINT uk_request_route_match UNIQUE (request_id, route_id)
);

CREATE TABLE IF NOT EXISTS outbox_events (
    id UUID PRIMARY KEY,
    event_type VARCHAR(128) NOT NULL,
    aggregate_type VARCHAR(64) NOT NULL,
    aggregate_id UUID NOT NULL,
    payload JSONB NOT NULL,
    status VARCHAR(32) NOT NULL,
    attempts INTEGER NOT NULL DEFAULT 0,
    next_attempt_at TIMESTAMP NOT NULL DEFAULT now(),
    created_at TIMESTAMP NOT NULL,
    published_at TIMESTAMP NULL,
    error_message TEXT NULL,
    CONSTRAINT chk_outbox_events_status
        CHECK (status IN ('NEW', 'PUBLISHED', 'FAILED')),
    CONSTRAINT uk_outbox_events_event_type_aggregate_id
        UNIQUE (event_type, aggregate_id)
);

CREATE TABLE IF NOT EXISTS earth_cells (
    cell_id BIGSERIAL PRIMARY KEY,
    cell_num BIGINT NOT NULL,
    longitude DOUBLE PRECISION,
    latitude DOUBLE PRECISION,
    importance DOUBLE PRECISION NOT NULL DEFAULT 0.0,
    contour_wkt TEXT NOT NULL,
    contour_geom geometry(Polygon, 4326)
);

CREATE TABLE IF NOT EXISTS request_cells (
    id BIGSERIAL PRIMARY KEY,
    request_id UUID NOT NULL,
    cell_id BIGINT NOT NULL,
    coverage_percent DOUBLE PRECISION,
    importance DOUBLE PRECISION NOT NULL,
    contour_wkt TEXT NOT NULL,
    contour_geom geometry(Polygon, 4326),
    CONSTRAINT fk_request_cells_request
        FOREIGN KEY (request_id)
        REFERENCES requests(id)
        ON DELETE CASCADE,
    CONSTRAINT fk_request_cells_cell
        FOREIGN KEY (cell_id)
        REFERENCES earth_cells(cell_id)
        ON DELETE CASCADE
);

CREATE TABLE IF NOT EXISTS earth_grid_settings (
    settings_id BIGINT PRIMARY KEY,
    calculation_step DOUBLE PRECISION NOT NULL
);

INSERT INTO earth_grid_settings(settings_id, calculation_step)
VALUES (1, 2.0)
ON CONFLICT (settings_id) DO NOTHING;

CREATE OR REPLACE FUNCTION fill_earth_cell_geometry()
RETURNS TRIGGER AS $$
BEGIN
    NEW.contour_geom := ST_GeomFromText(NEW.contour_wkt, 4326)::geometry(Polygon, 4326);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION fill_request_cell_geometry()
RETURNS TRIGGER AS $$
BEGIN
    NEW.contour_geom := ST_GeomFromText(NEW.contour_wkt, 4326)::geometry(Polygon, 4326);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION update_earth_cell_importance()
RETURNS TRIGGER AS $$
BEGIN
    UPDATE earth_cells earth_cell
    SET importance = (
        SELECT COALESCE(SUM(request_cell.importance * (COALESCE(request_cell.coverage_percent, 0.0) / 100.0)), 0.0)
        FROM request_cells request_cell
        WHERE request_cell.cell_id = COALESCE(NEW.cell_id, OLD.cell_id)
    )
    WHERE earth_cell.cell_id = COALESCE(NEW.cell_id, OLD.cell_id);

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS trg_fill_earth_cell_geometry ON earth_cells;
CREATE TRIGGER trg_fill_earth_cell_geometry
BEFORE INSERT OR UPDATE OF contour_wkt ON earth_cells
FOR EACH ROW EXECUTE FUNCTION fill_earth_cell_geometry();

DROP TRIGGER IF EXISTS trg_fill_request_cell_geometry ON request_cells;
CREATE TRIGGER trg_fill_request_cell_geometry
BEFORE INSERT OR UPDATE OF contour_wkt ON request_cells
FOR EACH ROW EXECUTE FUNCTION fill_request_cell_geometry();

DROP TRIGGER IF EXISTS request_cells_importance_trigger ON request_cells;
CREATE TRIGGER request_cells_importance_trigger
AFTER INSERT OR UPDATE OR DELETE ON request_cells
FOR EACH ROW EXECUTE FUNCTION update_earth_cell_importance();

CREATE INDEX IF NOT EXISTS idx_requests_status ON requests(status);
CREATE INDEX IF NOT EXISTS idx_requests_survey_type ON requests(survey_type);
CREATE INDEX IF NOT EXISTS idx_requests_begin_date_time ON requests(begin_date_time);
CREATE INDEX IF NOT EXISTS idx_requests_end_date_time ON requests(end_date_time);
CREATE INDEX IF NOT EXISTS idx_requests_deleted_at ON requests(deleted_at);
CREATE INDEX IF NOT EXISTS idx_requests_completed_at ON requests(completed_at);
CREATE INDEX IF NOT EXISTS idx_requests_cleanup_status_end ON requests(status, end_date_time);
CREATE INDEX IF NOT EXISTS idx_request_kpp_request_id ON request_kpp(request_id);
CREATE INDEX IF NOT EXISTS idx_request_kpp_kpp ON request_kpp(kpp);
CREATE INDEX IF NOT EXISTS idx_request_route_matches_route_id ON request_route_matches(route_id);
CREATE INDEX IF NOT EXISTS idx_outbox_events_status_created_at ON outbox_events(status, created_at);
CREATE INDEX IF NOT EXISTS idx_outbox_events_due ON outbox_events(event_type, status, next_attempt_at, created_at);
CREATE INDEX IF NOT EXISTS idx_earth_cells_cell_num ON earth_cells(cell_num);
CREATE INDEX IF NOT EXISTS idx_earth_cells_contour_geom ON earth_cells USING GIST (contour_geom);
CREATE INDEX IF NOT EXISTS idx_request_cells_request_id ON request_cells(request_id);
CREATE INDEX IF NOT EXISTS idx_request_cells_cell_id ON request_cells(cell_id);
CREATE INDEX IF NOT EXISTS idx_request_cells_contour_geom ON request_cells USING GIST (contour_geom);
