import mongoose, { Schema, models, model } from "mongoose";
import { SHOOTING_TYPES, ORDER_TAGS, USAGE_RIGHTS_OPTIONS, type ShootingType, type OrderTag, type UsageRights } from "@/lib/orderConstants";

export { SHOOTING_TYPES, ORDER_TAGS };
export type { ShootingType, OrderTag };

export interface OrderAttachment {
  filename: string;
  storedName: string;
  size: number;
  mimeType: string;
  uploadedAt: Date;
}

// Interne Vorbereitungs-Checkliste (nie dem Kunden sichtbar, wie internalNotes)
// — startet mit Standardpunkten (siehe DEFAULT_CHECKLIST_ITEMS in
// orderConstants.ts), Team kann weitere eigene Punkte ergänzen/entfernen.
export interface OrderChecklistItem {
  _id: string;
  text: string;
  done: boolean;
  doneAt: Date | null;
}

export interface OrderDoc {
  _id: string;
  clientId: mongoose.Types.ObjectId;
  tag: OrderTag;
  compensationType: "paid" | "tfp";
  shootingType: ShootingType;
  characterOrConcept: string;
  // Gesetzt, wenn der Auftrag zu einer konkreten Convention gehört (z. B. aus
  // einer genehmigten Warteliste-Anfrage) — u. a. für convention-gebundene
  // Rabattcodes.
  conventionId: mongoose.Types.ObjectId | null;
  // Freitext, vom Kunden selbst bei der Anfrage eingegeben (grobe
  // Vorstellung, z. B. "Convention" oder "bei mir zuhause") — bleibt
  // unangetastet, auch nachdem das Team unten eine genaue Adresse gesetzt hat.
  eventLocation: string;
  // Genaue Treffpunkt-Adresse, vom Team bei der Terminvergabe gesetzt (siehe
  // ShootDatePanel) — strukturiert wie die Rechnungsadresse, damit sie sich
  // sauber in Kalender-Exporte/Google Kalender übernehmen lässt.
  eventStreet: string;
  eventPostCode: string;
  eventCity: string;
  eventCountryCode: string;
  // Rein intern (nie an den Kunden ausgespielt, siehe effectiveEventAddress
  // in orderConstants.ts) — Team-Notiz zum Treffpunkt, z. B. Parkmöglichkeit
  // oder Zugang, nicht Teil der eigentlichen Adresse.
  eventAddressNote: string;
  datePeriod: string;
  // Vom Kunden gesetzt, wenn der Wunschzeitraum kürzer als die auf /angebote
  // angezeigte Mindestvorlaufzeit ist (siehe bookingSettings.minLeadTimeWeeks
  // in models/Settings.ts) — reine Absichtserklärung, ändert nichts an der
  // eigentlichen Terminvergabe. Nur das Team kann in ShootDatePanel einen so
  // frühen Termin tatsächlich bestätigen (confirmLeadTimeOverride).
  earlyDateRequested: boolean;
  earlyDateReason: string;
  instagramHandle: string;
  discordHandle: string;
  ideaMood: string;
  additionalNotes: string;
  // Nur fürs Team sichtbar, nie dem Kunden — getrennt vom Chat-Verlauf, der
  // ja mit dem Kunden geteilt wird.
  internalNotes: string;
  checklist: OrderChecklistItem[];
  // Nur bei Standard-Aufträgen relevant (TFP deckt das im ausführlichen
  // TFP-Vertrag schon ab) — vom Team gesetzt, fließt als Schnappschuss in
  // den Standard-Vertrag (siehe Contract.standardData) und wird in der
  // Foto-Ordner-Verwaltung angezeigt.
  usageRights: UsageRights;
  // Wer den Auftrag übernommen hat (z. B. bei mehreren Fotograf:innen im
  // Team) — bewusst selbst zuweisbar ("übernehmen") statt einer Dropdown-
  // Zuweisung an andere, damit klar bleibt, wer sich aktiv gemeldet hat.
  // Sowohl fürs Team (wer macht was) als auch dem Kunden sichtbar.
  assignedStaffId: mongoose.Types.ObjectId | null;
  // Nur gesetzt, wenn ein Teammitglied den Auftrag im Namen des Kunden
  // angelegt hat (siehe admin/orders/new) statt der Kunde selbst über
  // /account/new-order — rein informativ fürs Team, dem Kunden wird das
  // nirgends besonders angezeigt, der Auftrag wirkt für ihn wie jeder andere.
  createdByStaffId: mongoose.Types.ObjectId | null;
  attachments: OrderAttachment[];
  rejected: boolean;
  rejectionReason: string;
  rejectionNote: string;
  rejectedAt: Date | null;
  rejectedBy: "admin" | "client" | null;
  archived: boolean;
  // Echter Zahlungsstatus für die Buchhaltung — bewusst getrennt von
  // compensationType (das beschreibt nur "bezahlter Auftrag vs. TFP",
  // nicht ob das Geld tatsächlich eingegangen ist). Wird nie automatisch
  // gesetzt, nur explizit vom Admin.
  paid: boolean;
  paidAt: Date | null;
  paymentMethod: "" | "bank_transfer" | "paypal" | "online" | "cash" | "other";
  // Optionale Anzahlung — vom Team pro Auftrag gesetzt (0 = keine Anzahlung
  // verlangt). Getrennt von `paid`, damit ein Auftrag mit bezahlter Anzahlung
  // weiterhin als "offen" gilt, bis der Restbetrag da ist (siehe
  // createCheckoutSessionAction, das dann automatisch nur den Rest abrechnet).
  depositAmount: number;
  depositPaid: boolean;
  depositPaidAt: Date | null;
  // Manuell vom Team erfasst, falls Geld zurücküberwiesen wurde (z. B. nach
  // einer Stornierung) — kein automatischer Stripe-Refund-Call, nur
  // Nachweis/Buchhaltung (siehe EÜR-Export, der das vom Umsatz abzieht).
  refundedAmount: number;
  refundedAt: Date | null;
  refundNote: string;
  // Einmalig einlösbarer Rabattcode — discountAmount wird beim Einlösen anhand
  // des damals aktuellen Preisvorschlags berechnet und danach nicht mehr
  // automatisch neu berechnet (Schnappschuss, analog zu Invoice-Feldern).
  discountCode: string;
  discountAmount: number;
  // Rabatt, den das Team direkt am Auftrag gewährt (z. B. Kulanz) — bewusst
  // getrennt von discountCode/discountAmount, damit code- und manuell
  // gewährte Rabatte für Statistik/Nachvollziehbarkeit unterscheidbar
  // bleiben. Additiv: wirkt zusätzlich zu einem eingelösten Code.
  manualDiscountAmount: number;
  manualDiscountNote: string;
  // Kontoguthaben, das automatisch eingesetzt wurde, sobald ein
  // Preisvorschlag vorlag (Guthaben-System, 28.09.2026, siehe
  // lib/autoApplyCredit.ts) — Schnappschuss wie discountAmount, wird bei
  // einer späteren Preisänderung nicht automatisch neu berechnet. 0 =
  // noch nicht angewendet (auch der Auslöser für den einmaligen,
  // idempotenten Anwendungsversuch).
  creditApplied: number;
  // Summe aller bislang durch Mahnstufen hinzugekommenen Zusatzgebühren
  // (siehe models/Reminder.ts) — erhöht den tatsächlich zu zahlenden Betrag
  // (Direktzahlung/Anzeige), wird nie automatisch zurückgesetzt, auch nicht
  // nach Zahlungseingang (bleibt als Nachweis erhalten).
  reminderFeeTotal: number;
  // Vom Admin fest vereinbarter Termin — bewusst getrennt von `datePeriod`
  // (freitextlicher Wunschzeitraum des Kunden), damit der Kalender einen
  // echten, strukturierten Zeitpunkt zum Anzeigen hat.
  shootDate: Date | null;
  // Optionales Ende des Termins — Shootings dauern nicht immer gleich lang
  // (mal 2, mal 4 Std.), ohne Angabe nehmen Kalenderanzeige/-Export weiterhin
  // eine Stunde als Standarddauer an (siehe lib/googleCalendar.ts, lib/ics.ts).
  shootEndDate: Date | null;
  // Zeitstempel der automatischen Erinnerungs-Mail vor dem Termin (siehe
  // lib/preshootReminder.ts) — verhindert Doppelversand, falls der Cron
  // mehrmals im Erinnerungsfenster läuft. Wird zurückgesetzt, sobald sich
  // shootDate ändert, damit ein neu vereinbarter Termin wieder erinnert.
  preshootReminderSentAt: Date | null;
  // Zeitstempel des automatischen Wetter-Hinweises bei Outdoor-Shootings
  // (siehe lib/weatherCheck.ts) — verhindert Doppelversand, wird wie
  // preshootReminderSentAt bei geänderter shootDate zurückgesetzt.
  weatherWarningSentAt: Date | null;
  // ID des korrespondierenden Google-Calendar-Events, falls der
  // "Aufträge"-Systemkalender mit Google verbunden ist.
  googleEventId: string;
  // Schnappschuss der bei Anfrage gewählten Angebots-Preisstufe (falls über
  // /angebote gebucht, sonst alle leer/0) — rein informativ für den Admin
  // als Ausgangspunkt, die eigentliche Preisverhandlung läuft weiterhin
  // über PriceProposal. Bewusst als Schnappschuss statt Referenz, damit
  // spätere Änderungen an der Preisstufe alte Anfragen nicht verfälschen.
  bookedServiceTitle: string;
  bookedTierName: string;
  bookedTierPrice: number;
  // Analoger Schnappschuss, falls stattdessen ein Kombi-/Paket-Angebot
  // gebucht wurde (siehe models/Bundle.ts) — beide Schnappschuss-Paare sind
  // gegenseitig exklusiv, ein Auftrag füllt nur eines von beiden.
  bookedBundleTitle: string;
  bookedBundlePrice: number;
  createdAt: Date;
}

const attachmentSchema = new Schema<OrderAttachment>(
  {
    filename: { type: String, required: true },
    storedName: { type: String, required: true },
    size: { type: Number, required: true },
    mimeType: { type: String, required: true },
    uploadedAt: { type: Date, default: Date.now },
  },
  { _id: false }
);

const checklistItemSchema = new Schema<OrderChecklistItem>({
  text: { type: String, required: true, trim: true, maxlength: 200 },
  done: { type: Boolean, default: false },
  doneAt: { type: Date, default: null },
});

const orderSchema = new Schema<OrderDoc>(
  {
    clientId: { type: Schema.Types.ObjectId, ref: "Client", required: true, index: true },
    tag: { type: String, enum: ORDER_TAGS, default: "shoot_new_request" },
    compensationType: { type: String, enum: ["paid", "tfp"], default: "paid" },
    shootingType: { type: String, enum: SHOOTING_TYPES, required: true },
    characterOrConcept: { type: String, required: true, trim: true, maxlength: 300 },
    conventionId: { type: Schema.Types.ObjectId, ref: "Convention", default: null },
    eventLocation: { type: String, trim: true, maxlength: 300 },
    eventStreet: { type: String, default: "", trim: true, maxlength: 200 },
    eventPostCode: { type: String, default: "", trim: true, maxlength: 20 },
    eventCity: { type: String, default: "", trim: true, maxlength: 100 },
    eventCountryCode: { type: String, default: "DE", trim: true, maxlength: 2 },
    eventAddressNote: { type: String, default: "", trim: true, maxlength: 500 },
    datePeriod: { type: String, trim: true, maxlength: 300 },
    earlyDateRequested: { type: Boolean, default: false },
    earlyDateReason: { type: String, default: "", trim: true, maxlength: 500 },
    instagramHandle: { type: String, trim: true, maxlength: 300 },
    discordHandle: { type: String, trim: true, maxlength: 300 },
    ideaMood: { type: String, trim: true, maxlength: 2500 },
    additionalNotes: { type: String, trim: true, maxlength: 2500 },
    internalNotes: { type: String, default: "", trim: true, maxlength: 4000 },
    checklist: { type: [checklistItemSchema], default: [] },
    usageRights: { type: String, enum: USAGE_RIGHTS_OPTIONS, default: "" },
    assignedStaffId: { type: Schema.Types.ObjectId, ref: "Client", default: null },
    createdByStaffId: { type: Schema.Types.ObjectId, ref: "Client", default: null },
    attachments: { type: [attachmentSchema], default: [] },
    rejected: { type: Boolean, default: false },
    rejectionReason: { type: String, default: "", trim: true },
    rejectionNote: { type: String, default: "", trim: true, maxlength: 1000 },
    rejectedAt: { type: Date, default: null },
    rejectedBy: { type: String, enum: ["admin", "client", null], default: null },
    archived: { type: Boolean, default: false, index: true },
    paid: { type: Boolean, default: false },
    paidAt: { type: Date, default: null },
    paymentMethod: { type: String, enum: ["", "bank_transfer", "paypal", "online", "cash", "other"], default: "" },
    depositAmount: { type: Number, default: 0, min: 0 },
    depositPaid: { type: Boolean, default: false },
    depositPaidAt: { type: Date, default: null },
    refundedAmount: { type: Number, default: 0, min: 0 },
    refundedAt: { type: Date, default: null },
    refundNote: { type: String, default: "", trim: true, maxlength: 300 },
    discountCode: { type: String, default: "", trim: true, uppercase: true },
    discountAmount: { type: Number, default: 0, min: 0 },
    manualDiscountAmount: { type: Number, default: 0, min: 0 },
    creditApplied: { type: Number, default: 0, min: 0 },
    manualDiscountNote: { type: String, default: "", trim: true, maxlength: 300 },
    reminderFeeTotal: { type: Number, default: 0, min: 0 },
    shootDate: { type: Date, default: null, index: true },
    shootEndDate: { type: Date, default: null },
    preshootReminderSentAt: { type: Date, default: null },
    weatherWarningSentAt: { type: Date, default: null },
    googleEventId: { type: String, default: "" },
    bookedServiceTitle: { type: String, default: "" },
    bookedTierName: { type: String, default: "" },
    bookedTierPrice: { type: Number, default: 0 },
    bookedBundleTitle: { type: String, default: "" },
    bookedBundlePrice: { type: Number, default: 0 },
  },
  { timestamps: true }
);

export const Order = (models.Order as mongoose.Model<OrderDoc>) || model<OrderDoc>("Order", orderSchema);
