import { ENVIRONMENT_DEV } from "../protocol/index.mjs";
import { DEFAULT_GATEWAY_SESSION_ID, DEFAULT_PROJECT_ID } from "../sim/model.mjs";

export const PATCH_PANEL_SERVICE_ID = "hwlab-patch-panel";

const KNOWN_OUTPUT_FAMILIES = Object.freeze(["DO", "AO", "FREQ_OUT"]);
const SIGNAL_TARGET_FAMILY = Object.freeze({
  DO: "DI",
  AO: "AI",
  FREQ_OUT: "FREQ_IN"
});
const SIGNAL_VALUE_TYPE = Object.freeze({
  "DO->DI": "boolean",
  "AO->AI": "number",
  "FREQ_OUT->FREQ_IN": "number"
});

export function createWiringConfig({
  wiringConfigId = "wir_mvp_patch_panel",
  projectId = DEFAULT_PROJECT_ID,
  gatewaySessionId = DEFAULT_GATEWAY_SESSION_ID,
  name = "MVP simulated patch panel wiring",
  status = "active",
  connections,
  constraints,
  now = new Date().toISOString()
}) {
  if (!Array.isArray(connections) || connections.length === 0) {
    throw new Error("wiring config requires at least one connection");
  }

  return {
    wiringConfigId,
    projectId,
    gatewaySessionId,
    name,
    status,
    connections,
    constraints: constraints ?? {
      propagation: "patch-panel-only",
      localLoopbackSubstituteAllowed: false
    },
    createdAt: now,
    updatedAt: now
  };
}

export function createPatchPanelState({
  patchPanelStatusId = "pps_mvp_patch_panel",
  gatewaySessionId = null,
  wiringConfig,
  state = "active",
  metadata = {},
  now = new Date().toISOString()
}) {
  const effectiveGatewaySessionId =
    gatewaySessionId ?? wiringConfig.gatewaySessionId ?? DEFAULT_GATEWAY_SESSION_ID;

  return {
    patchPanelStatusId,
    projectId: wiringConfig.projectId ?? DEFAULT_PROJECT_ID,
    gatewaySessionId: effectiveGatewaySessionId,
    wiringConfigId: wiringConfig.wiringConfigId,
    serviceId: PATCH_PANEL_SERVICE_ID,
    state,
    environment: ENVIRONMENT_DEV,
    activeConnections: wiringConfig.connections.map((connection) => ({
      fromResourceId: connection.from.resourceId,
      fromPort: connection.from.port,
      toResourceId: connection.to.resourceId,
      toPort: connection.to.port
    })),
    observedAt: now,
    metadata: {
      propagation: "patch-panel-only",
      connectionCount: wiringConfig.connections.length,
      ...metadata
    }
  };
}

export function endpointKey(resourceId, port) {
  return `${resourceId}:${port}`;
}

export function classifySignalPort(port) {
  const normalized = String(port ?? "").trim().toUpperCase();
  if (!normalized) {
    return null;
  }

  const freqOut = normalized.match(/^FREQ_OUT(?<suffix>[A-Z0-9_-]*)$/);
  if (freqOut) {
    return {
      family: "FREQ_OUT",
      direction: "output",
      valueType: "number",
      suffix: freqOut.groups.suffix ?? ""
    };
  }

  const freqIn = normalized.match(/^FREQ_IN(?<suffix>[A-Z0-9_-]*)$/);
  if (freqIn) {
    return {
      family: "FREQ_IN",
      direction: "input",
      valueType: "number",
      suffix: freqIn.groups.suffix ?? ""
    };
  }

  const discrete = normalized.match(/^(?<family>DO|DI|AO|AI)(?<suffix>[A-Z0-9_-]*)$/);
  if (!discrete) {
    return null;
  }

  const family = discrete.groups.family;
  return {
    family,
    direction: family === "DO" || family === "AO" ? "output" : "input",
    valueType: family === "DO" || family === "DI" ? "boolean" : "number",
    suffix: discrete.groups.suffix ?? ""
  };
}

function issue(level, code, message, path, extra = {}) {
  return {
    level,
    code,
    message,
    path,
    ...extra
  };
}

function endpointDiagnostic(endpoint, path) {
  if (!endpoint || typeof endpoint !== "object" || Array.isArray(endpoint)) {
    return issue("error", "endpoint_invalid", "wiring endpoint must be an object", path);
  }
  if (!endpoint.resourceId || typeof endpoint.resourceId !== "string") {
    return issue("error", "endpoint_resource_missing", "wiring endpoint requires resourceId", `${path}.resourceId`);
  }
  if (!endpoint.port || typeof endpoint.port !== "string") {
    return issue("error", "endpoint_port_missing", "wiring endpoint requires port", `${path}.port`);
  }
  return null;
}

function describeExpectedTarget(fromFamily) {
  const targetFamily = SIGNAL_TARGET_FAMILY[fromFamily];
  if (!targetFamily) {
    return `one of ${KNOWN_OUTPUT_FAMILIES.join(", ")}`;
  }
  return targetFamily;
}

function normalizeConnection(connection, index) {
  const path = `$.connections[${index}]`;
  const diagnostics = [];

  if (!connection || typeof connection !== "object" || Array.isArray(connection)) {
    return {
      diagnostics: [issue("error", "connection_invalid", "wiring connection must be an object", path)],
      normalized: null
    };
  }

  for (const side of ["from", "to"]) {
    const endpointIssue = endpointDiagnostic(connection[side], `${path}.${side}`);
    if (endpointIssue) {
      diagnostics.push(endpointIssue);
    }
  }

  if (diagnostics.some((item) => item.level === "error")) {
    return { diagnostics, normalized: null };
  }

  const fromKind = classifySignalPort(connection.from.port);
  const toKind = classifySignalPort(connection.to.port);
  let syncKind = "generic";
  let valueType = "any";
  let syncable = false;

  if (fromKind || toKind) {
    if (!fromKind || !toKind) {
      diagnostics.push(
        issue(
          "error",
          "typed_signal_endpoint_mismatch",
          `typed signal wiring requires both endpoints to use signal ports; got ${connection.from.port} -> ${connection.to.port}`,
          path
        )
      );
    } else if (fromKind.direction !== "output") {
      diagnostics.push(
        issue(
          "error",
          "source_port_not_output",
          `${connection.from.port} is an input port and cannot drive ${connection.to.port}; expected source family ${KNOWN_OUTPUT_FAMILIES.join(", ")}`,
          `${path}.from.port`
        )
      );
    } else if (toKind.direction !== "input") {
      diagnostics.push(
        issue(
          "error",
          "target_port_not_input",
          `${connection.to.port} is an output port and cannot be driven by ${connection.from.port}; expected target family ${describeExpectedTarget(fromKind.family)}`,
          `${path}.to.port`
        )
      );
    } else if (SIGNAL_TARGET_FAMILY[fromKind.family] !== toKind.family) {
      diagnostics.push(
        issue(
          "error",
          "unsupported_signal_wiring",
          `${fromKind.family} output port ${connection.from.port} can only drive ${describeExpectedTarget(fromKind.family)} input ports; got ${connection.to.port}`,
          path
        )
      );
    } else {
      syncKind = `${fromKind.family}->${toKind.family}`;
      valueType = SIGNAL_VALUE_TYPE[syncKind];
      syncable = true;
    }
  }

  return {
    diagnostics,
    normalized: {
      index,
      from: connection.from,
      to: connection.to,
      mode: connection.mode ?? "exclusive",
      syncKind,
      valueType,
      syncable,
      bidirectionalRoute: syncKind === "generic"
    }
  };
}

export function validateWiringConfig(wiringConfig) {
  const diagnostics = [];
  const normalizedConnections = [];

  if (!wiringConfig || typeof wiringConfig !== "object" || Array.isArray(wiringConfig)) {
    return {
      diagnostics: [
        issue("error", "wiring_config_invalid", "wiring config must be an object", "$")
      ],
      normalizedConnections
    };
  }

  for (const field of ["wiringConfigId", "projectId", "gatewaySessionId", "status"]) {
    if (!wiringConfig[field] || typeof wiringConfig[field] !== "string") {
      diagnostics.push(
        issue("error", "wiring_config_field_missing", `wiring config requires string ${field}`, `$.${field}`)
      );
    }
  }

  if (wiringConfig.status !== "active") {
    diagnostics.push(
      issue(
        "error",
        "wiring_config_not_active",
        `patch-panel runtime can only apply active wiring configs; got ${JSON.stringify(wiringConfig.status)}`,
        "$.status"
      )
    );
  }

  if (wiringConfig.constraints?.propagation !== "patch-panel-only") {
    diagnostics.push(
      issue(
        "error",
        "wiring_propagation_invalid",
        "wiring config constraints.propagation must be patch-panel-only",
        "$.constraints.propagation"
      )
    );
  }

  if (wiringConfig.constraints?.localLoopbackSubstituteAllowed !== false) {
    diagnostics.push(
      issue(
        "error",
        "local_loopback_substitute_invalid",
        "wiring config must set constraints.localLoopbackSubstituteAllowed=false",
        "$.constraints.localLoopbackSubstituteAllowed"
      )
    );
  }

  if (!Array.isArray(wiringConfig.connections) || wiringConfig.connections.length === 0) {
    diagnostics.push(
      issue("error", "connections_missing", "wiring config requires at least one connection", "$.connections")
    );
  } else {
    wiringConfig.connections.forEach((connection, index) => {
      const result = normalizeConnection(connection, index);
      diagnostics.push(...result.diagnostics);
      if (result.normalized) {
        normalizedConnections.push(result.normalized);
      }
    });
  }

  return {
    diagnostics,
    normalizedConnections
  };
}

function diagnosticSummary(diagnostics) {
  return {
    errorCount: diagnostics.filter((item) => item.level === "error").length,
    warningCount: diagnostics.filter((item) => item.level === "warning").length,
    infoCount: diagnostics.filter((item) => item.level === "info").length
  };
}

function validateValueForConnection(value, connection) {
  if (connection.valueType === "boolean" && typeof value !== "boolean") {
    return issue(
      "error",
      "signal_value_type_mismatch",
      `${connection.syncKind} requires a boolean value; got ${typeof value}`,
      `$.connections[${connection.index}]`
    );
  }

  if (connection.valueType === "number" && typeof value !== "number") {
    return issue(
      "error",
      "signal_value_type_mismatch",
      `${connection.syncKind} requires a number value; got ${typeof value}`,
      `$.connections[${connection.index}]`
    );
  }

  return null;
}

export function createPatchPanel({ wiringConfig, now = () => new Date().toISOString() }) {
  const signals = new Map();
  const validation = validateWiringConfig(wiringConfig);
  const summary = diagnosticSummary(validation.diagnostics);

  function routeSignal({ fromResourceId, fromPort, value }) {
    const observedAt = now();
    const deliveries = [];
    const routeDiagnostics = [];

    if (summary.errorCount > 0) {
      return {
        accepted: false,
        propagatedBy: PATCH_PANEL_SERVICE_ID,
        deliveryCount: 0,
        deliveries,
        diagnostics: validation.diagnostics,
        observedAt
      };
    }

    for (const connection of validation.normalizedConnections) {
      const forwardMatch =
        connection.from.resourceId === fromResourceId && connection.from.port === fromPort;
      const reverseMatch =
        connection.bidirectionalRoute &&
        connection.to.resourceId === fromResourceId &&
        connection.to.port === fromPort;

      if (!forwardMatch && !reverseMatch) {
        continue;
      }

      const valueIssue = validateValueForConnection(value, connection);
      if (valueIssue) {
        routeDiagnostics.push(valueIssue);
        continue;
      }

      const target = forwardMatch ? connection.to : connection.from;
      const key = endpointKey(target.resourceId, target.port);
      const delivery = {
        resourceId: target.resourceId,
        port: target.port,
        value,
        sourceResourceId: fromResourceId,
        sourcePort: fromPort,
        observedAt
      };
      signals.set(key, delivery);
      deliveries.push(delivery);
    }

    if (routeDiagnostics.some((item) => item.level === "error")) {
      return {
        accepted: false,
        propagatedBy: PATCH_PANEL_SERVICE_ID,
        deliveryCount: deliveries.length,
        deliveries,
        diagnostics: routeDiagnostics,
        observedAt
      };
    }

    const response = {
      accepted: true,
      propagatedBy: PATCH_PANEL_SERVICE_ID,
      deliveryCount: deliveries.length,
      deliveries,
      observedAt
    };
    if (routeDiagnostics.length > 0) {
      response.diagnostics = routeDiagnostics;
    }
    return response;
  }

  function status() {
    return createPatchPanelState({
      wiringConfig,
      state: summary.errorCount > 0 ? "faulted" : "active",
      metadata: {
        diagnostics: summary,
        syncableConnectionCount: validation.normalizedConnections.filter((connection) => connection.syncable).length
      },
      now: now()
    });
  }

  return {
    wiringConfig,
    diagnostics: validation.diagnostics,
    diagnosticSummary: summary,
    normalizedConnections: validation.normalizedConnections,
    routeSignal,
    status,
    signals
  };
}
