package com.lingniu.ingest.facts;

import com.lingniu.ingest.api.ProtocolId;

import java.time.Instant;
import java.util.Map;

public sealed interface DecodedFact
        permits DecodedFact.Location,
                DecodedFact.Realtime,
                DecodedFact.Alarm,
                DecodedFact.Session,
                DecodedFact.Register,
                DecodedFact.Extension {

    String factId();
    String frameId();
    ProtocolId protocol();
    String vehicleKey();
    String vin();
    String phone();
    Instant eventTime();
    Instant receivedAt();
    String rawUri();
    Map<String, String> metadata();

    record Location(
            String factId,
            String frameId,
            ProtocolId protocol,
            String vehicleKey,
            String vin,
            String phone,
            Instant eventTime,
            Instant receivedAt,
            String rawUri,
            Map<String, String> metadata,
            Double longitude,
            Double latitude,
            Double altitudeM,
            Double speedKmh,
            Double directionDeg,
            Long alarmFlag,
            Long statusFlag,
            Double totalMileageKm
    ) implements DecodedFact {
        public Location {
            BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri);
            vin = BaseFields.clean(vin);
            phone = BaseFields.clean(phone);
            metadata = BaseFields.metadata(metadata);
        }
    }

    record Realtime(
            String factId,
            String frameId,
            ProtocolId protocol,
            String vehicleKey,
            String vin,
            String phone,
            Instant eventTime,
            Instant receivedAt,
            String rawUri,
            Map<String, String> metadata,
            Map<String, String> fields
    ) implements DecodedFact {
        public Realtime {
            BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri);
            vin = BaseFields.clean(vin);
            phone = BaseFields.clean(phone);
            metadata = BaseFields.metadata(metadata);
            fields = fields == null ? Map.of() : Map.copyOf(fields);
        }
    }

    record Alarm(
            String factId,
            String frameId,
            ProtocolId protocol,
            String vehicleKey,
            String vin,
            String phone,
            Instant eventTime,
            Instant receivedAt,
            String rawUri,
            Map<String, String> metadata,
            String alarmLevel,
            Long alarmCode,
            String alarmName,
            Map<String, String> fields
    ) implements DecodedFact {
        public Alarm {
            BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri);
            vin = BaseFields.clean(vin);
            phone = BaseFields.clean(phone);
            alarmLevel = BaseFields.clean(alarmLevel);
            alarmName = BaseFields.clean(alarmName);
            metadata = BaseFields.metadata(metadata);
            fields = fields == null ? Map.of() : Map.copyOf(fields);
        }
    }

    record Session(
            String factId,
            String frameId,
            ProtocolId protocol,
            String vehicleKey,
            String vin,
            String phone,
            Instant eventTime,
            Instant receivedAt,
            String rawUri,
            Map<String, String> metadata,
            String sessionType,
            Integer resultCode,
            Map<String, String> fields
    ) implements DecodedFact {
        public Session {
            BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri);
            vin = BaseFields.clean(vin);
            phone = BaseFields.clean(phone);
            sessionType = BaseFields.clean(sessionType);
            metadata = BaseFields.metadata(metadata);
            fields = fields == null ? Map.of() : Map.copyOf(fields);
        }
    }

    record Register(
            String factId,
            String frameId,
            ProtocolId protocol,
            String vehicleKey,
            String vin,
            String phone,
            Instant eventTime,
            Instant receivedAt,
            String rawUri,
            Map<String, String> metadata,
            Map<String, String> registrationFields
    ) implements DecodedFact {
        public Register {
            BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri);
            vin = BaseFields.clean(vin);
            phone = BaseFields.clean(phone);
            metadata = BaseFields.metadata(metadata);
            registrationFields = registrationFields == null ? Map.of() : Map.copyOf(registrationFields);
        }
    }

    record Extension(
            String factId,
            String frameId,
            ProtocolId protocol,
            String vehicleKey,
            String vin,
            String phone,
            Instant eventTime,
            Instant receivedAt,
            String rawUri,
            Map<String, String> metadata,
            String extensionType,
            Map<String, String> fields
    ) implements DecodedFact {
        public Extension {
            BaseFields.validate(factId, frameId, protocol, vehicleKey, eventTime, receivedAt, rawUri);
            vin = BaseFields.clean(vin);
            phone = BaseFields.clean(phone);
            extensionType = BaseFields.clean(extensionType);
            metadata = BaseFields.metadata(metadata);
            fields = fields == null ? Map.of() : Map.copyOf(fields);
        }
    }

    final class BaseFields {
        private BaseFields() {
        }

        static void validate(String factId,
                             String frameId,
                             ProtocolId protocol,
                             String vehicleKey,
                             Instant eventTime,
                             Instant receivedAt,
                             String rawUri) {
            required(factId, "factId");
            required(frameId, "frameId");
            if (protocol == null) {
                throw new IllegalArgumentException("protocol must not be null");
            }
            required(vehicleKey, "vehicleKey");
            if (eventTime == null) {
                throw new IllegalArgumentException("eventTime must not be null");
            }
            if (receivedAt == null) {
                throw new IllegalArgumentException("receivedAt must not be null");
            }
            required(rawUri, "rawUri");
        }

        static String clean(String value) {
            return value == null ? "" : value.trim();
        }

        static Map<String, String> metadata(Map<String, String> metadata) {
            return metadata == null ? Map.of() : Map.copyOf(metadata);
        }

        private static void required(String value, String field) {
            if (clean(value).isBlank()) {
                throw new IllegalArgumentException(field + " must not be blank");
            }
        }
    }
}
