import { logger } from "$lib/logger";
import {
  EXISTING_CLIENT_BOOKING_SCOPE,
  consumeBookingAccessToken,
  verifyBookingAccessToken,
} from "$lib/server/auth/booking-access-token";
import {
  getChannelTitle,
  sendAppointmentCreatedEmail,
  sendAppointmentRequestEmail,
} from "$lib/server/email/email-service";
import { registerOpenAPIRoute } from "$lib/server/openapi";
import { AppointmentService } from "$lib/server/services/appointment-service";
import { NotificationService } from "$lib/server/services/notification-service";
import { TenantAdminService } from "$lib/server/services/tenant-admin-service";
import {
  AuthenticationError,
  AuthorizationError,
  BackendError,
  InternalError,
  ValidationError,
  logError,
} from "$lib/server/utils/errors";
import type { AppointmentResponse } from "$lib/types/appointment";
import { json, type RequestHandler } from "@sveltejs/kit";
import { z } from "zod";

const requestSchema = z.object({
  emailHash: z.string(),
  tunnelId: z.string(),
  channelId: z.string(),
  agentId: z.string(),
  appointmentDate: z.string(),
  appointmentTimeZone: z.string(),
  duration: z.number().int().positive(),
  clientEmail: z.email().optional(),
  clientLanguage: z.string().optional().default("en"),
  salutation: z.string().optional(),
  encryptedAppointment: z.object({
    encryptedPayload: z.string(),
    iv: z.string(),
    authTag: z.string(),
  }),
});

type AddToTunnelRequest = z.infer<typeof requestSchema>;

async function requireBootstrapBookingAccessToken(
  request: Request,
  tenantId: string,
): Promise<NonNullable<Awaited<ReturnType<typeof verifyBookingAccessToken>>>> {
  const authorizationHeader = request.headers.get("Authorization");
  if (!authorizationHeader?.startsWith("Bearer ")) {
    throw new AuthenticationError("Bootstrap booking access token is required");
  }

  const token = authorizationHeader.substring("Bearer ".length).trim();
  if (!token) {
    throw new AuthenticationError("Bootstrap booking access token is required");
  }

  const tokenPayload = await verifyBookingAccessToken(token);
  if (!tokenPayload) {
    throw new AuthenticationError("Invalid or expired booking access token");
  }

  if (tokenPayload.scope !== EXISTING_CLIENT_BOOKING_SCOPE) {
    throw new AuthorizationError("Booking access token is not valid for new client bootstrap");
  }

  if (tokenPayload.tenantId !== tenantId) {
    throw new AuthorizationError("Booking access token is not valid for this tenant");
  }

  return tokenPayload;
}

function validateBootstrapTokenBinding(
  tokenPayload: NonNullable<Awaited<ReturnType<typeof verifyBookingAccessToken>>>,
  requestData: AddToTunnelRequest,
): void {
  if (tokenPayload.tunnelId !== requestData.tunnelId) {
    throw new AuthorizationError("Booking access token is not valid for this tunnel");
  }

  if (tokenPayload.emailHash && tokenPayload.emailHash !== requestData.emailHash) {
    throw new AuthorizationError("Booking access token is not valid for this email hash");
  }
}

// Register OpenAPI documentation for POST
registerOpenAPIRoute("/tenants/{id}/appointments/add-to-tunnel", "POST", {
  summary: "Add appointment to existing tunnel",
  description:
    "Adds a new appointment to an existing client tunnel. For clients who already have appointments and want to book another one.",
  tags: ["Appointments", "Clients"],
  parameters: [
    {
      name: "id",
      in: "path",
      required: true,
      schema: { type: "string", format: "uuid" },
      description: "Tenant ID",
    },
  ],
  requestBody: {
    description: "Appointment data for existing tunnel",
    content: {
      "application/json": {
        schema: {
          type: "object",
          properties: {
            emailHash: {
              type: "string",
              description: "SHA-256 hash of client email for verification",
              example: "a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3",
            },
            tunnelId: {
              type: "string",
              format: "uuid",
              description: "Existing client tunnel identifier",
              example: "550e8400-e29b-41d4-a716-446655440000",
            },
            channelId: {
              type: "string",
              format: "uuid",
              description: "Channel ID for the appointment",
              example: "550e8400-e29b-41d4-a716-446655440001",
            },
            appointmentDate: {
              type: "string",
              format: "date-time",
              description: "Appointment date and time (ISO 8601)",
              example: "2024-12-31T14:30:00.000Z",
            },
            clientEmail: {
              type: "string",
              format: "email",
              description: "Client email address for sending confirmation",
              example: "client@example.com",
            },
            clientLanguage: {
              type: "string",
              description: "Client's preferred language (de or en)",
              example: "de",
              default: "de",
            },
            encryptedAppointment: {
              type: "object",
              properties: {
                encryptedPayload: {
                  type: "string",
                  description: "AES-encrypted appointment data",
                  example: "deadbeef123456789abcdef...",
                },
                iv: {
                  type: "string",
                  description: "Initialization vector for AES encryption",
                  example: "123456789abcdef...",
                },
                authTag: {
                  type: "string",
                  description: "Authentication tag for AES-GCM",
                  example: "fedcba9876543210...",
                },
              },
              required: ["encryptedPayload", "iv", "authTag"],
              description: "Encrypted appointment data",
            },
          },
          required: [
            "emailHash",
            "tunnelId",
            "channelId",
            "appointmentDate",
            "encryptedAppointment",
          ],
        },
      },
    },
  },
  responses: {
    "200": {
      description: "Appointment added to tunnel successfully",
      content: {
        "application/json": {
          schema: {
            type: "object",
            properties: {
              id: {
                type: "string",
                format: "uuid",
                description: "Created appointment ID",
                example: "550e8400-e29b-41d4-a716-446655440002",
              },
              appointmentDate: {
                type: "string",
                format: "date-time",
                description: "Appointment date and time",
                example: "2024-12-31T14:30:00.000Z",
              },
              status: {
                type: "string",
                enum: ["NEW", "CONFIRMED"],
                description: "Initial appointment status (depends on channel configuration)",
                example: "NEW",
              },
            },
            required: ["id", "appointmentDate", "status"],
          },
        },
      },
    },
    "400": {
      description: "Invalid request data",
      content: {
        "application/json": {
          schema: { $ref: "#/components/schemas/Error" },
        },
      },
    },
    "404": {
      description: "Tunnel or channel not found",
      content: {
        "application/json": {
          schema: { $ref: "#/components/schemas/Error" },
        },
      },
    },
    "409": {
      description: "Selected doctor is no longer available for this time slot",
      content: {
        "application/json": {
          schema: { $ref: "#/components/schemas/Error" },
          example: {
            error:
              "Selected doctor is no longer available for this time slot. Please choose a different time.",
          },
        },
      },
    },
    "500": {
      description: "Internal server error",
      content: {
        "application/json": {
          schema: { $ref: "#/components/schemas/Error" },
        },
      },
    },
  },
});

/**
 * POST /api/tenants/[id]/appointments/add-to-tunnel
 *
 * Adds a new appointment to an existing client tunnel.
 * For clients who already have appointments and want to book another one.
 */
export const POST: RequestHandler = async ({ request, params }) => {
  try {
    const tenantId = params.id;
    if (!tenantId) {
      throw new ValidationError("Tenant ID is required");
    }

    const body = await request.json();
    const validatedData = requestSchema.parse(body);

    const tokenPayload = await requireBootstrapBookingAccessToken(request, tenantId);

    validateBootstrapTokenBinding(tokenPayload, validatedData);

    if (validatedData.salutation) {
      throw new ValidationError("Bees incoming");
    }

    logger.debug("Adding appointment to existing tunnel", {
      tenantId,
      tunnelId: validatedData.tunnelId,
      appointmentDate: validatedData.appointmentDate,
    });

    const appointmentService = await AppointmentService.forTenant(tenantId);
    const response: AppointmentResponse = await appointmentService.addAppointmentToTunnel({
      emailHash: validatedData.emailHash,
      tunnelId: validatedData.tunnelId,
      channelId: validatedData.channelId,
      agentId: validatedData.agentId,
      appointmentDate: validatedData.appointmentDate,
      appointmentTimeZone: validatedData.appointmentTimeZone,
      duration: validatedData.duration,
      clientEmail: validatedData.clientEmail || "",
      clientLanguage: validatedData.clientLanguage,
      encryptedAppointment: validatedData.encryptedAppointment,
      incomingTunnelId: validatedData.tunnelId,
    });

    const createdAppointment = await appointmentService.getAppointmentById(response.id);

    logger.info("Successfully added appointment to tunnel", {
      tenantId,
      tunnelId: validatedData.tunnelId,
      appointmentId: response.id,
    });

    // Send email notification to client
    try {
      const requiresConfirmation = response.requiresConfirmation || false;

      // Get tenant information
      const tenantService = await TenantAdminService.getTenantById(tenantId);
      const tenant = tenantService.tenantData;

      if (!tenant) {
        logger.warn("Cannot send email: Tenant not found", { tenantId });
      } else {
        // Get channel title for the email
        const channelTitle = await getChannelTitle(
          tenantId,
          validatedData.channelId,
          validatedData.clientLanguage,
        );

        // Send appropriate email based on whether confirmation is required
        if (requiresConfirmation) {
          // send notification to tenant staff about new appointment request
          const notificationService = NotificationService.forTenant(tenantId);
          (await notificationService).createNotification({
            type: "APPOINTMENT_REQUESTED",
            channelId: validatedData.channelId,
            metaData: { appointmentId: response.id },
          });
        }
        if (validatedData.clientEmail) {
          // Create client data object from request
          const clientData = {
            email: validatedData.clientEmail,
            language: validatedData.clientLanguage,
          };

          if (requiresConfirmation) {
            await sendAppointmentRequestEmail(clientData, tenant, createdAppointment, channelTitle);
            logger.debug("Appointment request email sent", {
              tunnelId: validatedData.tunnelId,
              appointmentId: response.id,
              tenantId,
            });
          } else {
            await sendAppointmentCreatedEmail(clientData, tenant, createdAppointment, channelTitle);
            logger.debug("Appointment confirmation email sent", {
              tunnelId: validatedData.tunnelId,
              appointmentId: response.id,
              tenantId,
            });
          }
        }
      }
    } catch (emailError) {
      logger.error("Failed to send appointment notification email", {
        appointmentId: response.id,
        tenantId,
        error: String(emailError),
      });
      // Don't throw - email failure shouldn't fail the appointment creation
    }
    await consumeBookingAccessToken(tokenPayload);
    return json(response);
  } catch (error) {
    logError(logger)("Failed to add appointment to tunnel", error);

    if (error instanceof z.ZodError) {
      return json({ error: "Invalid request data", details: error.issues }, { status: 400 });
    }

    if (error instanceof BackendError) {
      logError(logger)(error.message, error);
      return error.toJson();
    }

    return new InternalError().toJson();
  }
};
