import { Router } from "express";
import { z } from "zod";

import { HttpError } from "../lib/http-error.js";
import {
  requireAnyPermission,
  requireAuth,
  requireOrg,
  requirePermission,
} from "../middleware/auth.js";
import { recordActivity } from "../services/activity.js";
import * as claims from "../services/integrations/claims.js";
import {
  getConfig,
  getCredentials,
  type IntegrationType,
  INTEGRATION_TYPES,
  listConfigs,
  saveConfig,
} from "../services/integrations/config.js";
import * as eprescribe from "../services/integrations/eprescribe.js";
import * as fhir from "../services/integrations/fhir.js";

export const integrationsRouter = Router();

function parseType(value: string): IntegrationType {
  if ((INTEGRATION_TYPES as readonly string[]).includes(value)) {
    return value as IntegrationType;
  }
  throw new HttpError(404, "Unknown integration.");
}

function assertAdmin(role: string | undefined): void {
  const isAdmin = String(role ?? "")
    .split(",")
    .map((s) => s.trim())
    .some((r) => r === "owner" || r === "admin");
  if (!isAdmin) {
    throw new HttpError(403, "Only owners and admins can change integrations.");
  }
}

// Test a connection against the credentials/endpoint the type's service expects.
function bearerFromCreds(raw: string | null): string | null {
  if (!raw) return null;
  try {
    return (JSON.parse(raw) as { token?: string }).token ?? null;
  } catch {
    return raw.trim() || null;
  }
}

// --- Config (read for any member; write for owners/admins) ------------------

integrationsRouter.get(
  "/",
  requireAuth,
  requireOrg,
  async (req, res, next) => {
    try {
      res.json(await listConfigs(req.organizationId!));
    } catch (err) {
      next(err);
    }
  },
);

const configSchema = z.object({
  endpoint: z.string().trim().max(2048).optional(),
  enabled: z.boolean().optional(),
  // Empty string clears the stored secret; omitted leaves it unchanged.
  credentials: z.string().max(8192).optional(),
});

integrationsRouter.put(
  "/:type",
  requireAuth,
  requireOrg,
  async (req, res, next) => {
    try {
      assertAdmin(req.memberRole);
      const type = parseType(String(req.params.type));
      const input = configSchema.parse(req.body);
      const saved = await saveConfig(req.organizationId!, type, input);
      void recordActivity({
        orgId: req.organizationId!,
        actor: { id: req.user!.id, name: req.user!.name },
        action: `Updated the ${type} integration`,
        entityType: "patient",
      });
      res.json(saved);
    } catch (err) {
      next(err);
    }
  },
);

integrationsRouter.post(
  "/:type/test",
  requireAuth,
  requireOrg,
  async (req, res, next) => {
    try {
      assertAdmin(req.memberRole);
      const type = parseType(String(req.params.type));
      const orgId = req.organizationId!;
      const config = await getConfig(orgId, type);
      const token = bearerFromCreds(await getCredentials(orgId, type));
      const tester =
        type === "fhir"
          ? fhir.testConnection
          : type === "eprescribe"
            ? eprescribe.testConnection
            : claims.testConnection;
      res.json(await tester(config.endpoint, token));
    } catch (err) {
      next(err);
    }
  },
);

// --- Actions ----------------------------------------------------------------

const syncSchema = z.object({ fileNumber: z.string().trim().min(1) });

// Pull lab results for a patient from the FHIR server.
integrationsRouter.post(
  "/fhir/sync",
  requireAuth,
  requireOrg,
  requireAnyPermission({ patient: ["write"] }, { lab: ["write"] }),
  async (req, res, next) => {
    try {
      const { fileNumber } = syncSchema.parse(req.body);
      res.json(await fhir.syncLabs(req.organizationId!, fileNumber));
    } catch (err) {
      next(err);
    }
  },
);

const ingestSchema = z.object({
  fileNumber: z.string().trim().min(1),
  message: z.string().min(1),
});

// Ingest a raw HL7 v2 ORU result message for a patient.
integrationsRouter.post(
  "/fhir/ingest",
  requireAuth,
  requireOrg,
  requireAnyPermission({ patient: ["write"] }, { lab: ["write"] }),
  async (req, res, next) => {
    try {
      const { fileNumber, message } = ingestSchema.parse(req.body);
      res.json(await fhir.ingestHl7(req.organizationId!, fileNumber, message));
    } catch (err) {
      next(err);
    }
  },
);

const sendRxSchema = z.object({ rxId: z.string().trim().min(1) });

// Transmit a prescription to a pharmacy (NCPDP SCRIPT NewRx).
integrationsRouter.post(
  "/eprescribe/send",
  requireAuth,
  requireOrg,
  requirePermission({ prescription: ["write"] }),
  async (req, res, next) => {
    try {
      const { rxId } = sendRxSchema.parse(req.body);
      res.json(await eprescribe.sendRx(req.organizationId!, rxId));
    } catch (err) {
      next(err);
    }
  },
);

const submitClaimSchema = z.object({ invoiceId: z.string().trim().min(1) });

// Submit an insurance claim for an invoice (X12 837P) + read the remittance.
integrationsRouter.post(
  "/claims/submit",
  requireAuth,
  requireOrg,
  requirePermission({ invoice: ["write"] }),
  async (req, res, next) => {
    try {
      const { invoiceId } = submitClaimSchema.parse(req.body);
      res.json(await claims.submitClaim(req.organizationId!, invoiceId));
    } catch (err) {
      next(err);
    }
  },
);
