package com.lingniu.ingest.identity;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lingniu.ingest.api.ProtocolId;
import com.lingniu.ingest.identity.config.VehicleIdentityProperties;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Locale;
import java.util.Map;

public final class MySqlVehicleIdentityService implements VehicleIdentityResolver, VehicleIdentityRegistry {

    private final JdbcTemplate jdbc;
    private final ObjectMapper mapper;
    private final String tableName;

    public MySqlVehicleIdentityService(DataSource dataSource,
                                       VehicleIdentityProperties.Mysql properties,
                                       ObjectMapper mapper) {
        this.jdbc = new JdbcTemplate(dataSource);
        this.mapper = mapper == null ? new ObjectMapper() : mapper;
        VehicleIdentityProperties.Mysql mysql = properties == null ? new VehicleIdentityProperties.Mysql() : properties;
        this.tableName = safeTableName(mysql.getTableName());
        if (mysql.isInitializeSchema()) {
            initializeSchema();
        }
    }

    @Override
    public void bind(VehicleIdentityBinding binding) {
        String metadataJson = metadataJson(binding.metadata());
        jdbc.update("""
                        INSERT INTO %s
                        (protocol, vin, phone, device_id, plate, province, city, maker, device_type, plate_color, metadata_json,
                         created_at, updated_at)
                        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
                        ON DUPLICATE KEY UPDATE
                          vin = VALUES(vin),
                          device_id = VALUES(device_id),
                          province = VALUES(province),
                          city = VALUES(city),
                          maker = VALUES(maker),
                          device_type = VALUES(device_type),
                          plate_color = VALUES(plate_color),
                          metadata_json = VALUES(metadata_json),
                          updated_at = CURRENT_TIMESTAMP
                        """.formatted(tableName),
                protocol(binding.protocol()), binding.vin(), binding.phone(), binding.deviceId(), binding.plate(),
                binding.metadata().getOrDefault("province", ""),
                binding.metadata().getOrDefault("city", ""),
                binding.metadata().getOrDefault("maker", ""),
                binding.metadata().getOrDefault("deviceType", ""),
                binding.metadata().getOrDefault("plateColor", ""),
                metadataJson);
    }

    @Override
    public VehicleIdentity resolve(VehicleIdentityLookup lookup) {
        if (lookup == null) {
            return new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN);
        }
        if (!lookup.vin().isBlank()) {
            return new VehicleIdentity(lookup.vin(), true, VehicleIdentitySource.EXPLICIT_VIN);
        }
        VehicleIdentity byPhone = resolveBy("phone", lookup.protocol(), lookup.phone(), VehicleIdentitySource.BOUND_PHONE);
        if (byPhone != null) {
            return byPhone;
        }
        VehicleIdentity byDevice = resolveBy("device_id", lookup.protocol(), lookup.deviceId(), VehicleIdentitySource.BOUND_DEVICE_ID);
        if (byDevice != null) {
            return byDevice;
        }
        VehicleIdentity byPlate = resolveBy("plate", lookup.protocol(), lookup.plate(), VehicleIdentitySource.BOUND_PLATE);
        if (byPlate != null) {
            return byPlate;
        }
        if (!lookup.deviceId().isBlank()) {
            return new VehicleIdentity(lookup.deviceId(), false, VehicleIdentitySource.FALLBACK_DEVICE_ID);
        }
        if (!lookup.phone().isBlank()) {
            return new VehicleIdentity(lookup.phone(), false, VehicleIdentitySource.FALLBACK_PHONE);
        }
        if (!lookup.plate().isBlank()) {
            return new VehicleIdentity(lookup.plate(), false, VehicleIdentitySource.FALLBACK_PLATE);
        }
        return new VehicleIdentity("unknown", false, VehicleIdentitySource.UNKNOWN);
    }

    private VehicleIdentity resolveBy(String column, ProtocolId protocol, String externalId, VehicleIdentitySource source) {
        if (externalId == null || externalId.isBlank()) {
            return null;
        }
        String safeColumn = switch (column) {
            case "phone", "device_id", "plate" -> column;
            default -> throw new IllegalArgumentException("unsupported identity lookup column: " + column);
        };
        List<VehicleIdentity> matches = jdbc.query("""
                        SELECT vin
                        FROM %s
                        WHERE protocol = ? AND %s = ?
                        ORDER BY updated_at DESC
                        LIMIT 1
                        """.formatted(tableName, safeColumn),
                (rs, rowNum) -> identity(rs, source),
                protocol(protocol), externalId.trim());
        return matches.isEmpty() ? null : matches.getFirst();
    }

    private VehicleIdentity identity(ResultSet rs, VehicleIdentitySource source) throws SQLException {
        return new VehicleIdentity(rs.getString("vin"), true, source);
    }

    private void initializeSchema() {
        jdbc.execute("""
                CREATE TABLE IF NOT EXISTS %s (
                    id BIGINT AUTO_INCREMENT PRIMARY KEY,
                    protocol VARCHAR(32) NOT NULL,
                    vin VARCHAR(32) NOT NULL,
                    phone VARCHAR(64) NOT NULL DEFAULT '',
                    device_id VARCHAR(64) NOT NULL DEFAULT '',
                    plate VARCHAR(64) NOT NULL DEFAULT '',
                    province VARCHAR(32) NOT NULL DEFAULT '',
                    city VARCHAR(32) NOT NULL DEFAULT '',
                    maker VARCHAR(64) NOT NULL DEFAULT '',
                    device_type VARCHAR(128) NOT NULL DEFAULT '',
                    plate_color VARCHAR(32) NOT NULL DEFAULT '',
                    metadata_json TEXT,
                    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                    UNIQUE KEY uk_vehicle_identity_protocol_phone (protocol, phone),
                    KEY idx_vehicle_identity_protocol_device (protocol, device_id),
                    KEY idx_vehicle_identity_protocol_plate (protocol, plate)
                )
                """.formatted(tableName));
        ensureColumn("phone", "phone VARCHAR(64) NOT NULL DEFAULT ''");
        ensureColumn("device_id", "device_id VARCHAR(64) NOT NULL DEFAULT ''");
        ensureColumn("plate", "plate VARCHAR(64) NOT NULL DEFAULT ''");
        ensureColumn("province", "province VARCHAR(32) NOT NULL DEFAULT ''");
        ensureColumn("city", "city VARCHAR(32) NOT NULL DEFAULT ''");
        ensureColumn("maker", "maker VARCHAR(64) NOT NULL DEFAULT ''");
        ensureColumn("device_type", "device_type VARCHAR(128) NOT NULL DEFAULT ''");
        ensureColumn("plate_color", "plate_color VARCHAR(32) NOT NULL DEFAULT ''");
        ensureColumn("metadata_json", "metadata_json TEXT");
        ensureColumn("created_at", "created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP");
        ensureColumn("updated_at", "updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP");
        ensureIndex("idx_vehicle_identity_protocol_device", "CREATE INDEX idx_vehicle_identity_protocol_device ON %s (protocol, device_id)");
        ensureIndex("idx_vehicle_identity_protocol_plate", "CREATE INDEX idx_vehicle_identity_protocol_plate ON %s (protocol, plate)");
    }

    private void ensureColumn(String columnName, String definition) {
        if (!columnExists(columnName)) {
            jdbc.execute("ALTER TABLE " + tableName + " ADD COLUMN " + definition);
        }
    }

    private boolean columnExists(String columnName) {
        return Boolean.TRUE.equals(jdbc.execute((ConnectionCallback<Boolean>) connection -> {
            DatabaseMetaData metaData = connection.getMetaData();
            for (String table : nameVariants(tableName)) {
                for (String column : nameVariants(columnName)) {
                    try (ResultSet rs = metaData.getColumns(null, null, table, column)) {
                        if (rs.next()) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }));
    }

    private void ensureIndex(String indexName, String ddlTemplate) {
        if (!indexExists(indexName)) {
            jdbc.execute(ddlTemplate.formatted(tableName));
        }
    }

    private boolean indexExists(String indexName) {
        return Boolean.TRUE.equals(jdbc.execute((ConnectionCallback<Boolean>) connection -> {
            DatabaseMetaData metaData = connection.getMetaData();
            for (String table : nameVariants(tableName)) {
                try (ResultSet rs = metaData.getIndexInfo(null, null, table, false, false)) {
                    while (rs.next()) {
                        String existing = rs.getString("INDEX_NAME");
                        if (existing != null && existing.equalsIgnoreCase(indexName)) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }));
    }

    private static String[] nameVariants(String name) {
        return new String[]{
                name,
                name.toLowerCase(Locale.ROOT),
                name.toUpperCase(Locale.ROOT)
        };
    }

    private String metadataJson(Map<String, String> metadata) {
        if (metadata == null || metadata.isEmpty()) {
            return "{}";
        }
        try {
            return mapper.writeValueAsString(metadata);
        } catch (JsonProcessingException e) {
            throw new IllegalArgumentException("vehicle identity metadata serialize failed", e);
        }
    }

    private static String protocol(ProtocolId protocol) {
        return protocol == null ? "UNKNOWN" : protocol.name();
    }

    private static String safeTableName(String value) {
        String name = value == null || value.isBlank() ? "vehicle_identity_bindings" : value.trim();
        if (!name.matches("[A-Za-z0-9_]+")) {
            throw new IllegalArgumentException("invalid vehicle identity mysql table name: " + value);
        }
        return name.toLowerCase(Locale.ROOT);
    }
}
