generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

// ==================== PDF TEMPLATES (Auftragsvorlagen) ====================

model PdfTemplate {
  id              Int       @id @default(autoincrement())
  name            String    @unique              // z.B. "EWE Auftragsformular"
  description     String?                        // Beschreibung
  providerName    String?                        // Zugehöriger Anbieter (z.B. "EWE")
  templatePath    String                         // Pfad zur PDF-Vorlage
  originalName    String                         // Originaler Dateiname
  // Feld-Mapping: JSON-Objekt { pdfFieldName: crmFieldPath }
  // z.B. { "Vorname": "customer.firstName", "PLZ": "customer.addresses[0].postalCode" }
  fieldMapping    String    @db.LongText         // JSON
  // Rufnummern-Konfiguration
  phoneFieldPrefix String?                       // Prefix für Rufnummern-Felder (z.B. "Rufnummer")
  maxPhoneFields  Int?      @default(8)          // Max. Rufnummern-Felder im PDF
  isActive        Boolean   @default(true)
  createdAt       DateTime  @default(now())
  updatedAt       DateTime  @updatedAt
}

// ==================== EMAIL LOG ====================

model EmailLog {
  id              Int       @id @default(autoincrement())
  // Absender & Empfänger
  fromAddress     String                      // Absender-E-Mail
  toAddress       String                      // Empfänger-E-Mail
  subject         String                      // Betreff
  // Versand-Kontext
  context         String                      // z.B. "consent-link", "authorization-request", "customer-email"
  customerId      Int?                        // Zugehöriger Kunde (falls vorhanden)
  triggeredBy     String?                     // Wer hat den Versand ausgelöst (User-Email)
  // SMTP-Details
  smtpServer      String                      // SMTP-Server
  smtpPort        Int                         // SMTP-Port
  smtpEncryption  String                      // SSL, STARTTLS, NONE
  smtpUser        String                      // SMTP-Benutzername
  // Ergebnis
  success         Boolean                     // Erfolgreich?
  messageId       String?                     // Message-ID aus SMTP-Antwort
  errorMessage    String?   @db.Text          // Fehlermeldung bei Fehler
  smtpResponse    String?   @db.Text          // SMTP-Server-Antwort
  // Zeitstempel
  sentAt          DateTime  @default(now())

  @@index([sentAt])
  @@index([customerId])
  @@index([success])
}

// ==================== APP SETTINGS ====================

model AppSetting {
  id        Int      @id @default(autoincrement())
  key       String   @unique
  value     String   @db.Text
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

// ==================== USERS & AUTH ====================

model User {
  id                 Int       @id @default(autoincrement())
  email              String    @unique
  password           String
  firstName          String
  lastName           String
  isActive           Boolean   @default(true)
  tokenInvalidatedAt DateTime? // Zeitpunkt ab dem alle Tokens ungültig sind (für Zwangslogout bei Rechteänderung)

  // Passwort-Reset
  passwordResetToken     String?   @unique
  passwordResetExpiresAt DateTime?

  // Messaging-Kanäle (für Datenschutz-Link-Versand)
  whatsappNumber     String?
  telegramUsername   String?
  signalNumber       String?

  customerId         Int?      @unique
  customer           Customer? @relation(fields: [customerId], references: [id])
  roles              UserRole[]
  // Persönliche Anrede-Präferenz pro Kunde ("Du"/"Sie") – User-Wunsch
  // 2026-07-04. Fehlender Eintrag = Kunden-Default (Customer.useInformalAddress).
  customerSalutations UserCustomerSalutation[]
  createdAt          DateTime  @default(now())
  updatedAt          DateTime  @updatedAt
}

model Role {
  id          Int              @id @default(autoincrement())
  name        String           @unique
  description String?
  permissions RolePermission[]
  users       UserRole[]
  createdAt   DateTime         @default(now())
  updatedAt   DateTime         @updatedAt
}

model Permission {
  id       Int              @id @default(autoincrement())
  resource String
  action   String
  roles    RolePermission[]

  @@unique([resource, action])
}

model RolePermission {
  roleId       Int
  permissionId Int
  role         Role       @relation(fields: [roleId], references: [id], onDelete: Cascade)
  permission   Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)

  @@id([roleId, permissionId])
}

model UserRole {
  userId Int
  roleId Int
  user   User @relation(fields: [userId], references: [id], onDelete: Cascade)
  role   Role @relation(fields: [roleId], references: [id], onDelete: Cascade)

  @@id([userId, roleId])
}

// ==================== CUSTOMERS ====================

enum CustomerType {
  PRIVATE
  BUSINESS
}

model Customer {
  id                           Int            @id @default(autoincrement())
  customerNumber               String         @unique
  type                         CustomerType   @default(PRIVATE)
  salutation                   String?
  firstName                    String
  lastName                     String
  companyName                  String?
  foundingDate                 DateTime?      // Gründungsdatum (für Firmen)
  birthDate                    DateTime?
  birthPlace                   String?
  email                        String?
  phone                        String?
  mobile                       String?
  taxNumber                    String?
  // Kleinunternehmer nach §19 UStG bzw. sonst USt-befreit: dann wird der
  // (Firmen-)Kunde bei Gutschriften wie ein Privatkunde behandelt (keine USt),
  // bis er ggf. USt-pflichtig wird. Steuert nur die VORBELEGUNG neuer
  // Gutschriften; bereits erstellte Gutschriften speichern ihren eigenen
  // Snapshot und bleiben von einem späteren Wechsel unberührt.
  vatExempt                    Boolean        @default(false)
  businessRegistrationPath     String?        // PDF-Pfad zur Gewerbeanmeldung
  commercialRegisterPath       String?        // PDF-Pfad zum Handelsregisterauszug
  commercialRegisterNumber     String?        // Handelsregisternummer (Text)
  privacyPolicyPath            String?        // PDF-Pfad zur Datenschutzerklärung (für alle Kunden)
  consentHash                  String?        @unique  // Hash für öffentlichen Einwilligungslink /datenschutz/<hash>
  consentHashExpiresAt         DateTime?      // Pentest 57.7: TTL für Public-Consent-Link (30 Tage Default); nach Ablauf ist getCustomerByConsentHash null und der Link muss neu generiert werden.
  notes                        String?        @db.Text

  // ===== Portal-Zugangsdaten =====
  portalEnabled                Boolean        @default(false)  // Portal aktiviert?
  portalEmail                  String?        @unique          // Portal-Login E-Mail
  portalPasswordHash           String?                         // Gehashtes Passwort (für Login)
  portalPasswordEncrypted      String?                         // Verschlüsseltes Passwort (für Anzeige)
  portalLastLogin              DateTime?                       // Letzte Anmeldung

  // Portal Passwort-Reset
  portalPasswordResetToken     String?        @unique
  portalPasswordResetExpiresAt DateTime?
  // Portal Session-Invalidation (nach Passwort-Reset / Rechte-Änderung)
  portalTokenInvalidatedAt     DateTime?
  // Einmalpasswort: gesetzt durch "Zugangsdaten versenden"-Button. Beim ersten
  // erfolgreichen Login wird der Hash sofort gelöscht (OTP verbraucht) und
  // Frontend in Force-Change-Password-Flow geleitet.
  portalPasswordMustChange     Boolean        @default(false)

  // Geburtstagsmodal: Jahr in dem dem Kunden der Geburtstagsgruß gezeigt wurde (vermeidet mehrfaches Anzeigen)
  lastBirthdayGreetingYear     Int?

  // Anrede-Verhältnis: true = Du (informell), false = Sie (formell, Default)
  useInformalAddress           Boolean        @default(false)

  // Automatischer Geburtstagsgruß-Versand
  autoBirthdayGreeting         Boolean        @default(false)
  autoBirthdayChannel          String?        // "email", "whatsapp", "telegram", "signal"

  user                 User?
  addresses            Address[]
  bankCards            BankCard[]
  identityDocuments    IdentityDocument[]
  meters               Meter[]
  stressfreiEmails     StressfreiEmail[]
  contracts            Contract[]

  // Vertreter-Beziehungen (Kunde kann für andere Kunden handeln)
  representingFor      CustomerRepresentative[] @relation("RepresentativeCustomer")
  representedBy        CustomerRepresentative[] @relation("RepresentedCustomer")

  // Vollmachten
  authorizationsGiven    RepresentativeAuthorization[] @relation("AuthorizationCustomer")
  authorizationsReceived RepresentativeAuthorization[] @relation("AuthorizationRepresentative")

  // DSGVO: Einwilligungen
  consents             CustomerConsent[]

  // Persönliche Anrede-Präferenzen aller Mitarbeiter zu diesem Kunden.
  userSalutations      UserCustomerSalutation[]

  // Werbung: wer hat wen an Board geholt.
  // referralAsRecruited = dieser Kunde WURDE geworben (max. 1 Werber).
  // referralsAsRecruiter = Kunden, die DIESER Kunde geworben hat (beliebig viele).
  referralAsRecruited  CustomerReferral?  @relation("ReferralRecruited")
  referralsAsRecruiter CustomerReferral[] @relation("ReferralRecruiter")

  createdAt            DateTime       @default(now())
  updatedAt            DateTime       @updatedAt
}

// Werbe-Beziehung zwischen zwei Kunden ("Geworben / angeworben").
// Ein Datensatz = ein Werber (recruiter) hat einen Kunden (recruited) an
// Board geholt, plus die Beziehungsart. `recruitedId` ist @unique, damit ein
// Kunde nur von EINER Person geworben sein kann (Abschnitt 1 in der UI).
// Ein Werber kann beliebig viele Kunden werben (Abschnitt 2).
model CustomerReferral {
  id            Int      @id @default(autoincrement())
  recruiterId   Int
  recruiter     Customer @relation("ReferralRecruiter", fields: [recruiterId], references: [id], onDelete: Cascade)
  recruitedId   Int      @unique
  recruited     Customer @relation("ReferralRecruited", fields: [recruitedId], references: [id], onDelete: Cascade)
  relationship  String   // Ehepartner/in, Kind, Freund/Kumpel, …
  createdAt     DateTime @default(now())
  createdBy     String?

  @@index([recruiterId])
}

// ==================== USER-CUSTOMER SALUTATION ====================
// Per-User-Präferenz, ob der eingeloggte Mitarbeiter mit dem jeweiligen
// Kunden per Du oder Sie ist. Fehlender Eintrag → fällt auf den
// Kunden-Default (Customer.useInformalAddress) zurück.
// User-Wunsch 2026-07-04: Stefan kann per Du mit einem Kunden sein,
// während andere Mitarbeiter denselben Kunden weiter siezen.

model UserCustomerSalutation {
  userId     Int
  user       User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  customerId Int
  customer   Customer @relation(fields: [customerId], references: [id], onDelete: Cascade)
  // 'DU' oder 'SIE'. String statt Enum, damit man ohne Migration einen
  // dritten Wert nachschieben könnte (z.B. 'MIXED' o.ä.).
  preference String
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt

  @@id([userId, customerId])
  @@index([customerId])
}

// ==================== CUSTOMER REPRESENTATIVES ====================
// Vertretungsbeziehung: Ein Kunde kann die Verträge eines anderen Kunden einsehen
// z.B. Sohn (representativeId) kann Verträge der Mutter (customerId) sehen

model CustomerRepresentative {
  id                Int       @id @default(autoincrement())
  customerId        Int                           // Der Kunde, dessen Verträge eingesehen werden (z.B. Mutter)
  customer          Customer  @relation("RepresentedCustomer", fields: [customerId], references: [id], onDelete: Cascade)
  representativeId  Int                           // Der Kunde, der einsehen darf (z.B. Sohn)
  representative    Customer  @relation("RepresentativeCustomer", fields: [representativeId], references: [id], onDelete: Cascade)
  notes             String?                       // Notizen zur Vertretung
  isActive          Boolean   @default(true)
  createdAt         DateTime  @default(now())
  updatedAt         DateTime  @updatedAt

  @@unique([customerId, representativeId])        // Keine doppelten Einträge
}

// ==================== VOLLMACHTEN ====================
// Vollmacht: Kunde B erteilt Kunde A die Vollmacht, seine Daten einzusehen
// Ohne Vollmacht kann der Vertreter die Verträge des Kunden NICHT sehen

model RepresentativeAuthorization {
  id                Int       @id @default(autoincrement())
  customerId        Int                           // Der Kunde, der die Vollmacht erteilt (z.B. Mutter)
  customer          Customer  @relation("AuthorizationCustomer", fields: [customerId], references: [id], onDelete: Cascade)
  representativeId  Int                           // Der Vertreter, der Zugriff bekommt (z.B. Sohn)
  representative    Customer  @relation("AuthorizationRepresentative", fields: [representativeId], references: [id], onDelete: Cascade)
  isGranted         Boolean   @default(false)     // Vollmacht erteilt?
  grantedAt         DateTime?                     // Wann erteilt
  withdrawnAt       DateTime?                     // Wann widerrufen
  source            String?                       // Quelle: 'portal', 'papier', 'crm-backend'
  documentPath      String?                       // PDF-Upload der unterschriebenen Vollmacht
  notes             String?   @db.Text            // Notizen
  createdAt         DateTime  @default(now())
  updatedAt         DateTime  @updatedAt

  @@unique([customerId, representativeId])        // Eine Vollmacht pro Paar
}

// ==================== ADDRESSES ====================

enum AddressType {
  DELIVERY_RESIDENCE
  BILLING
}

model Address {
  id          Int         @id @default(autoincrement())
  customerId  Int
  customer    Customer    @relation(fields: [customerId], references: [id], onDelete: Cascade)
  type        AddressType @default(DELIVERY_RESIDENCE)
  street      String
  houseNumber String
  postalCode  String
  city        String
  country     String      @default("Deutschland")
  isDefault   Boolean     @default(false)
  // Marktlokations-IDs (MaLo) je Sparte – die MaLo gehört zur (Liefer-)Adresse,
  // nicht zum Vertrag. Strom- und Gas-Marktlokation sind getrennt.
  maloIdElectricity String? // MaLo-ID Strom
  maloIdGas         String? // MaLo-ID Gas
  // Eigentümer (leer = Kunde ist selbst Eigentümer)
  ownerCompany    String?
  ownerFirstName  String?
  ownerLastName   String?
  ownerStreet     String?
  ownerHouseNumber String?
  ownerPostalCode String?
  ownerCity       String?
  ownerPhone      String?
  ownerMobile     String?
  ownerEmail      String?
  contractsAsDelivery   Contract[] @relation("DeliveryAddress")
  contractsAsBilling    Contract[] @relation("BillingAddress")
  meters                Meter[]
  createdAt   DateTime    @default(now())
  updatedAt   DateTime    @updatedAt
}

// ==================== BANK CARDS ====================

model BankCard {
  id            Int        @id @default(autoincrement())
  customerId    Int
  customer      Customer   @relation(fields: [customerId], references: [id], onDelete: Cascade)
  accountHolder String
  iban          String
  cardNumber    String?    // Karten-/Kontokartennummer (optional, z.B. Debitkarte)
  bic           String?
  bankName      String?
  expiryDate    DateTime?
  documentPath  String?    // Pfad zur hochgeladenen PDF
  description   String?    @db.Text  // Freitext-Notiz (z.B. "Geschäftskonto", "gemeinsames Konto mit Partner")
  isActive      Boolean    @default(true)
  contracts     Contract[]
  creditNotePayouts CreditNote[] @relation("CreditNotePayoutBankCard")
  createdAt     DateTime   @default(now())
  updatedAt     DateTime   @updatedAt
}

// ==================== IDENTITY DOCUMENTS ====================

enum DocumentType {
  ID_CARD
  PASSPORT
  DRIVERS_LICENSE
  OTHER
}

model IdentityDocument {
  id               Int          @id @default(autoincrement())
  customerId       Int
  customer         Customer     @relation(fields: [customerId], references: [id], onDelete: Cascade)
  type             DocumentType @default(ID_CARD)
  documentNumber   String
  issuingAuthority String?
  issueDate        DateTime?
  expiryDate       DateTime?
  documentPath     String?      // Pfad zur hochgeladenen PDF
  isActive         Boolean      @default(true)
  // Führerschein-spezifische Felder
  licenseClasses   String?      // z.B. "B, BE, AM, L" - kommasepariert
  licenseIssueDate DateTime?    // Datum des Führerscheinerwerbs (Klasse B)
  contracts        Contract[]
  createdAt        DateTime     @default(now())
  updatedAt        DateTime     @updatedAt
}

// ==================== EMAIL PROVIDER CONFIG (Plesk, cPanel etc.) ====================

enum EmailProviderType {
  PLESK
  CPANEL
  DIRECTADMIN
}

// Verschlüsselungstyp für E-Mail-Verbindungen
enum MailEncryption {
  SSL       // Implicit SSL/TLS (Ports 465/993) - Verschlüsselung von Anfang an
  STARTTLS  // STARTTLS (Ports 587/143) - Startet unverschlüsselt, dann Upgrade
  NONE      // Keine Verschlüsselung (Ports 25/143)
}

model EmailProviderConfig {
  id                    Int               @id @default(autoincrement())
  name                  String            @unique  // z.B. "Plesk Hauptserver"
  type                  EmailProviderType
  apiUrl                String                     // API-URL (z.B. https://server.de:8443)
  apiKey                String?                    // API-Key (verschlüsselt)
  username              String?                    // Benutzername für API
  passwordEncrypted     String?                    // Passwort (verschlüsselt)
  domain                String                     // Domain für E-Mails (z.B. stressfrei-wechseln.de)
  defaultForwardEmail   String?                    // Standard-Weiterleitungsadresse (unsere eigene)

  // IMAP/SMTP-Server für E-Mail-Client (optional, default: mail.{domain})
  imapServer            String?                    // z.B. "mail.stressfrei-wechseln.de"
  imapPort              Int?              @default(993)
  smtpServer            String?
  smtpPort              Int?              @default(465)

  // Verschlüsselungs-Einstellungen
  imapEncryption        MailEncryption    @default(SSL)    // SSL, STARTTLS oder NONE
  smtpEncryption        MailEncryption    @default(SSL)    // SSL, STARTTLS oder NONE
  allowSelfSignedCerts  Boolean           @default(false)  // Selbstsignierte Zertifikate erlauben

  // System-E-Mail für automatisierte Nachrichten (z.B. DSGVO Consent-Links)
  systemEmailAddress             String?          // z.B. "info@stressfrei-wechseln.de"
  systemEmailPasswordEncrypted   String?          // Passwort (verschlüsselt)

  // Label für Kunden-E-Mail-Adressen in der UI (z.B. "Stressfrei-Wechseln")
  // Wenn leer, wird automatisch aus der Domain abgeleitet (z.B. "stressfrei-wechseln.de" → "Stressfrei-Wechseln")
  customerEmailLabel             String?

  isActive              Boolean           @default(true)
  isDefault             Boolean           @default(false)  // Standard-Provider
  createdAt             DateTime          @default(now())
  updatedAt             DateTime          @updatedAt
}

// ==================== STRESSFREI-WECHSELN EMAIL ADDRESSES ====================

model StressfreiEmail {
  id                     Int           @id @default(autoincrement())
  customerId             Int
  customer               Customer      @relation(fields: [customerId], references: [id], onDelete: Cascade)
  email                  String                      // Die Weiterleitungs-E-Mail-Adresse
  platform               String?                     // Für welche Plattform (z.B. "Freenet", "Klarmobil")
  notes                  String?       @db.Text      // Optionale Notizen
  isActive               Boolean       @default(true)
  isProvisioned          Boolean       @default(false)  // Wurde bei Provider angelegt?
  provisionedAt          DateTime?                   // Wann wurde provisioniert?
  provisionError         String?       @db.Text      // Fehlermeldung falls Provisionierung fehlschlug

  // Mailbox-Zugangsdaten (für IMAP/SMTP-Zugang)
  hasMailbox             Boolean       @default(false)  // Hat echte Mailbox (nicht nur Weiterleitung)?
  emailPasswordEncrypted String?                        // Verschlüsseltes Mailbox-Passwort (AES-256-GCM)

  // Zusätzliche Weiterleitungsziele (über die Stamm-E-Mail des Kunden
  // hinaus). Wird beim Sync zusammen mit customer.email +
  // config.defaultForwardEmail an den Provider geschickt. JSON-Array
  // von Strings, z.B. `["info@partner.de","cc@kanzlei.de"]`.
  additionalForwardingEmails String?  @db.Text

  contracts              Contract[]                  // Verträge die diese E-Mail als Benutzername verwenden
  cachedEmails           CachedEmail[]               // Gecachte E-Mails aus dieser Mailbox
  createdAt              DateTime      @default(now())
  updatedAt              DateTime      @updatedAt
}

// ==================== CACHED EMAILS (E-Mail-Client) ====================

enum EmailFolder {
  INBOX
  SENT
  SPAM
}

model CachedEmail {
  id                Int              @id @default(autoincrement())
  stressfreiEmailId Int
  stressfreiEmail   StressfreiEmail  @relation(fields: [stressfreiEmailId], references: [id], onDelete: Cascade)

  // Ordner (Posteingang oder Gesendet)
  folder            EmailFolder      @default(INBOX)

  // IMAP-Identifikation
  messageId         String           // RFC 5322 Message-ID
  uid               Int              // IMAP UID (für Synchronisierung, bei SENT = 0)

  // E-Mail-Metadaten
  subject           String?
  fromAddress       String
  fromName          String?
  toAddresses       String           @db.Text  // JSON Array
  ccAddresses       String?          @db.Text  // JSON Array
  receivedAt        DateTime

  // Inhalt
  textBody          String?          @db.LongText
  htmlBody          String?          @db.LongText
  hasAttachments    Boolean          @default(false)
  attachmentNames   String?          @db.Text  // JSON Array

  // Vertragszuordnung
  contractId        Int?
  contract          Contract?        @relation(fields: [contractId], references: [id], onDelete: SetNull)
  assignedAt        DateTime?
  assignedBy        Int?             // User ID der die Zuordnung gemacht hat
  isAutoAssigned    Boolean          @default(false) // true = automatisch beim Senden aus Vertrag

  // Flags
  isRead            Boolean          @default(false)
  isStarred         Boolean          @default(false)

  // Papierkorb
  isDeleted         Boolean          @default(false)  // Im Papierkorb?
  deletedAt         DateTime?                         // Wann gelöscht?

  createdAt         DateTime         @default(now())
  updatedAt         DateTime         @updatedAt

  @@unique([stressfreiEmailId, messageId, folder]) // Folder hinzugefügt: gleiche MessageID kann in INBOX und SENT existieren
  @@index([contractId])
  @@index([stressfreiEmailId, folder, receivedAt])
  @@index([stressfreiEmailId, isDeleted])           // Für Papierkorb-Abfragen
}

// ==================== METERS (Energy) ====================

enum MeterType {
  ELECTRICITY
  GAS
}

enum MeterTariffModel {
  SINGLE       // Eintarifzähler (Standard)
  DUAL         // Zweitarifzähler (HT/NT)
}

model Meter {
  id            Int              @id @default(autoincrement())
  customerId    Int
  customer      Customer         @relation(fields: [customerId], references: [id], onDelete: Cascade)
  // Lieferadresse, an der der Zähler hängt. Optional, weil Bestandszähler
  // vor dem Feature noch keine Adresse haben (werden manuell nachgepflegt).
  addressId     Int?
  address       Address?         @relation(fields: [addressId], references: [id], onDelete: SetNull)
  meterNumber   String
  type          MeterType
  tariffModel   MeterTariffModel @default(SINGLE)  // Eintarif oder Zweitarif (HT/NT)
  location      String?
  isActive      Boolean          @default(true)
  // Folgezähler-Kette: zeigt auf den Vorgänger, den dieser Zähler abgelöst hat.
  // Wird beim Anlegen als Folgezähler gesetzt; informational + zum Anzeigen
  // der Kette. Auto-Propagation auf Verträge passiert beim Create.
  predecessorMeterId Int?
  predecessor   Meter?           @relation("MeterSuccessor", fields: [predecessorMeterId], references: [id], onDelete: SetNull)
  successors    Meter[]          @relation("MeterSuccessor")
  readings        MeterReading[]
  energyDetails   EnergyContractDetails[]
  contractMeters  ContractMeter[] @relation("ContractMeters")
  createdAt       DateTime       @default(now())
  updatedAt     DateTime       @updatedAt
}

model MeterReading {
  id          Int                    @id @default(autoincrement())
  meterId     Int
  meter       Meter                  @relation(fields: [meterId], references: [id], onDelete: Cascade)
  readingDate DateTime
  value       Float                  // Bei Eintarif: Gesamtwert. Bei Zweitarif: HT-Wert
  valueNt     Float?                 // Nur bei Zweitarif: NT-Wert (Niedertarif)
  unit        String                 @default("kWh")
  notes       String?
  // Meldung & Übertragung
  reportedBy  String?                // Wer hat gemeldet? (E-Mail des Portal-Kunden oder Mitarbeiter)
  status      MeterReadingStatus     @default(RECORDED)
  transferredAt DateTime?            // Wann wurde der Stand an den Anbieter übertragen?
  transferredBy String?              // Wer hat übertragen?
  createdAt   DateTime               @default(now())
}

enum MeterReadingStatus {
  RECORDED      // Erfasst (vom Mitarbeiter)
  REPORTED      // Vom Kunden gemeldet (Portal)
  TRANSFERRED   // An Anbieter übertragen
}

// ==================== SALES PLATFORMS ====================

model SalesPlatform {
  id          Int        @id @default(autoincrement())
  name        String     @unique
  contactInfo String?    @db.Text
  isActive    Boolean    @default(true)
  contracts   Contract[]
  createdAt   DateTime   @default(now())
  updatedAt   DateTime   @updatedAt
}

// ==================== CANCELLATION PERIODS ====================

model CancellationPeriod {
  id          Int        @id @default(autoincrement())
  code        String     @unique  // z.B. "14T", "1M", "3M", "12M", "1J"
  description String               // z.B. "14 Tage", "1 Monat", "3 Monate"
  isActive    Boolean    @default(true)
  contracts   Contract[]
  createdAt   DateTime   @default(now())
  updatedAt   DateTime   @updatedAt
}

// ==================== CONTRACT DURATIONS ====================

model ContractDuration {
  id          Int        @id @default(autoincrement())
  code        String     @unique  // z.B. "12M", "24M", "1J", "2J"
  description String               // z.B. "12 Monate", "24 Monate", "1 Jahr"
  isActive    Boolean    @default(true)
  contracts   Contract[]
  createdAt   DateTime   @default(now())
  updatedAt   DateTime   @updatedAt
}

// ==================== PROVIDERS (Anbieter) ====================

model Provider {
  id                  Int        @id @default(autoincrement())
  name                String     @unique    // Anbietername
  portalUrl           String?               // Kundenkontourl (Login-Seite)
  usernameFieldName   String?               // Benutzernamefeld (z.B. "email", "username")
  passwordFieldName   String?               // Kennwortfeld (z.B. "password", "pwd")
  // Kontaktdaten beim Anbieter (für CRM-Mitarbeiter zum Nachschlagen)
  contactEmail        String?               // Allgemeine Kontakt-Emailadresse
  contactPhone        String?               // Kontakt-Telefonnummer
  contactFax          String?               // Kontakt-Faxnummer
  contactAddress      String?    @db.Text   // Kontakt-Postadresse (mehrzeilig)
  // Dedizierte Kündigungs-Endpunkte (wenn separat vom allgemeinen Kontakt)
  cancellationEmail   String?               // Kündigungs-Emailadresse
  cancellationFax     String?               // Kündigungs-Faxnummer
  cancellationAddress String?    @db.Text   // Kündigungs-Postadresse (mehrzeilig)
  isActive            Boolean    @default(true)
  tariffs             Tariff[]
  contracts           Contract[]
  previousContracts   Contract[] @relation("PreviousProvider")  // Verträge wo dieser Provider Altanbieter ist
  createdAt           DateTime   @default(now())
  updatedAt           DateTime   @updatedAt
}

// ==================== TARIFFS (Tarife) ====================

model Tariff {
  id          Int        @id @default(autoincrement())
  providerId  Int
  provider    Provider   @relation(fields: [providerId], references: [id], onDelete: Cascade)
  name        String                       // Tarifname
  isActive    Boolean    @default(true)
  contracts   Contract[]
  createdAt   DateTime   @default(now())
  updatedAt   DateTime   @updatedAt

  @@unique([providerId, name])  // Eindeutiger Tarif pro Anbieter
}

// ==================== CONTRACT CATEGORIES ====================

model ContractCategory {
  id          Int        @id @default(autoincrement())
  code        String     @unique    // Technischer Code (z.B. ELECTRICITY, GAS)
  name        String                // Anzeigename (z.B. Strom, Gas)
  icon        String?               // Icon-Name für UI (z.B. "Zap", "Flame")
  color       String?               // Farbe für UI (z.B. "#FFC107")
  sortOrder   Int        @default(0)
  isActive    Boolean    @default(true)
  contracts   Contract[]
  createdAt   DateTime   @default(now())
  updatedAt   DateTime   @updatedAt
}

// ==================== CONTRACTS ====================

// Legacy Enum - wird durch ContractCategory ersetzt
enum ContractType {
  ELECTRICITY
  GAS
  DSL
  CABLE
  FIBER
  MOBILE
  TV
  CAR_INSURANCE
}

enum ContractStatus {
  DRAFT
  PENDING
  ACTIVE
  // Kündigungsschreiben raus, Bestätigung des Anbieters steht noch aus.
  CANCELLED
  // Kündigung vom Anbieter bestätigt (Bestätigungsdatum/-dokument liegt vor).
  CANCELLED_CONFIRMED
  EXPIRED
  DEACTIVATED
  // "Läuft weiter auf unbestimmte Zeit" – Vertrag ist unbefristet aktiv.
  // Cockpit-Fristen (Kündigungsvorlauf, Verlängerungs-Reminder etc.)
  // ignorieren diesen Status, weil es kein Ende gibt, das getriggered
  // werden könnte.
  ONGOING
}

model Contract {
  id                      Int             @id @default(autoincrement())
  contractNumber          String          @unique
  customerId              Int
  customer                Customer        @relation(fields: [customerId], references: [id], onDelete: Cascade)
  type                    ContractType
  status                  ContractStatus  @default(DRAFT)

  // Neue konfigurierbare Kategorie (ersetzt langfristig das type-Enum)
  contractCategoryId      Int?
  contractCategory        ContractCategory? @relation(fields: [contractCategoryId], references: [id])

  // Lieferadresse
  addressId               Int?
  address                 Address?        @relation("DeliveryAddress", fields: [addressId], references: [id])

  // Rechnungsadresse (falls leer, wird Lieferadresse verwendet)
  billingAddressId        Int?
  billingAddress          Address?        @relation("BillingAddress", fields: [billingAddressId], references: [id])

  bankCardId              Int?
  bankCard                BankCard?       @relation(fields: [bankCardId], references: [id])

  identityDocumentId      Int?
  identityDocument        IdentityDocument? @relation(fields: [identityDocumentId], references: [id])

  salesPlatformId         Int?
  salesPlatform           SalesPlatform?  @relation(fields: [salesPlatformId], references: [id])

  cancellationPeriodId    Int?
  cancellationPeriod      CancellationPeriod? @relation(fields: [cancellationPeriodId], references: [id])

  contractDurationId      Int?
  contractDuration        ContractDuration? @relation(fields: [contractDurationId], references: [id])

  previousContractId      Int?            @unique
  previousContract        Contract?       @relation("ContractHistory", fields: [previousContractId], references: [id])
  followUpContract        Contract?       @relation("ContractHistory")

  // Altanbieter-Daten (nur wenn kein Vorgängervertrag existiert)
  previousProviderId      Int?
  previousProvider        Provider?       @relation("PreviousProvider", fields: [previousProviderId], references: [id])
  previousCustomerNumber  String?         // Kundennummer beim Altanbieter
  previousContractNumber  String?         // Vertragsnummer beim Altanbieter

  // Anbieter & Tarif (neue Verknüpfung)
  providerId              Int?
  provider                Provider?       @relation(fields: [providerId], references: [id])
  tariffId                Int?
  tariff                  Tariff?         @relation(fields: [tariffId], references: [id])

  // Legacy-Felder (für Abwärtskompatibilität)
  providerName            String?
  tariffName              String?
  customerNumberAtProvider String?
  contractNumberAtProvider String?         // Vertragsnummer beim Anbieter
  orderNumberAtSalesPlatform    String?    // Auftragsnummer bei der Vertriebsplattform
  customerNumberAtSalesPlatform String?    // Kundennummer bei der Vertriebsplattform
  contractNumberAtSalesPlatform String?    // Vertragsnummer bei der Vertriebsplattform
  priceFirst12Months      String?         // Preis erste 12 Monate
  priceFrom13Months       String?         // Preis ab 13. Monat
  priceAfter24Months      String?         // Preis nach 24 Monaten

  startDate               DateTime?
  endDate                 DateTime?       // Wird aus startDate + contractDuration berechnet
  commission              Float?

  // Kündigungsdokumente
  cancellationLetterPath              String?   // Kündigungsschreiben PDF
  cancellationConfirmationPath        String?   // Kündigungsbestätigung PDF
  cancellationLetterOptionsPath       String?   // Kündigungsschreiben Optionen PDF
  cancellationConfirmationOptionsPath String?   // Kündigungsbestätigung Optionen PDF

  // Kündigungsdaten
  cancellationConfirmationDate        DateTime? // Kündigungsbestätigungsdatum
  cancellationConfirmationOptionsDate DateTime? // Kündigungsbestätigungsoptionendatum
  wasSpecialCancellation              Boolean   @default(false) // Wurde sondergekündigt?

  portalUsername          String?
  portalPasswordEncrypted String?

  // Stressfrei-Wechseln E-Mail als Benutzername (Alternative zu portalUsername)
  stressfreiEmailId       Int?
  stressfreiEmail         StressfreiEmail? @relation(fields: [stressfreiEmailId], references: [id])

  // Portaldaten bewusst nicht hinterlegt (Anbieter hat kein Portal oder
  // Kunde bekommt keine Zugangsdaten). Wenn true, unterdrückt das Cockpit
  // die „Portal-Zugangsdaten fehlen"-Warnung für diesen Vertrag.
  portalCredentialsNotRequired Boolean @default(false)

  // Snooze: Vertrag zurückstellen bis Datum (für Cockpit)
  nextReviewDate          DateTime?       // Erneute Prüfung am

  notes                   String?         @db.Text

  energyDetails           EnergyContractDetails?
  internetDetails         InternetContractDetails?
  mobileDetails           MobileContractDetails?
  tvDetails               TvContractDetails?
  carInsuranceDetails     CarInsuranceDetails?

  tasks                   ContractTask[]
  assignedEmails          CachedEmail[]   // Zugeordnete E-Mails aus dem E-Mail-Client
  historyEntries          ContractHistoryEntry[]
  documents               ContractDocument[]
  invoices                Invoice[]
  creditNotes             CreditNote[]

  createdAt               DateTime        @default(now())
  updatedAt               DateTime        @updatedAt
}

// ==================== CONTRACT DOCUMENTS ====================

model ContractDocument {
  id            Int       @id @default(autoincrement())
  contractId    Int
  contract      Contract  @relation(fields: [contractId], references: [id], onDelete: Cascade)
  documentType  String                    // Auftragsformular, Lieferbestätigung, etc.
  documentPath  String                    // Dateipfad
  originalName  String                    // Originaler Dateiname
  notes         String?   @db.Text
  uploadedBy    String?                   // Wer hat hochgeladen
  createdAt     DateTime  @default(now())

  @@index([contractId])
}

// ==================== CONTRACT HISTORY ====================

model ContractHistoryEntry {
  id          Int      @id @default(autoincrement())
  contractId  Int
  contract    Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
  title       String                     // Kurzbeschreibung (z.B. "Folgevertrag erstellt", "kWh auf 18000 erhöht")
  description String?  @db.Text          // Längere Beschreibung (optional)
  isAutomatic Boolean  @default(false)   // true = automatisch erstellt, false = manuell
  createdBy   String                     // E-Mail des Erstellers
  createdAt   DateTime @default(now())
}

// ==================== CONTRACT TASKS ====================

enum ContractTaskStatus {
  OPEN
  COMPLETED
}

model ContractTask {
  id              Int                   @id @default(autoincrement())
  // Nullable: Aufgaben können auch OHNE Vertrag/Kunde angelegt werden
  // (allgemeine interne Aufgabe). Ohne Vertrag → kein Kunde → nie im Portal.
  contractId      Int?
  contract        Contract?             @relation(fields: [contractId], references: [id], onDelete: Cascade)
  title           String
  description     String?               @db.Text
  status          ContractTaskStatus    @default(OPEN)
  visibleInPortal Boolean               @default(false)
  createdBy       String?               // Name des Erstellers
  completedAt     DateTime?
  subtasks        ContractTaskSubtask[]
  createdAt       DateTime              @default(now())
  updatedAt       DateTime              @updatedAt
}

model ContractTaskSubtask {
  id          Int                @id @default(autoincrement())
  taskId      Int
  task        ContractTask       @relation(fields: [taskId], references: [id], onDelete: Cascade)
  title       String
  status      ContractTaskStatus @default(OPEN)
  createdBy   String?
  completedAt DateTime?
  createdAt   DateTime           @default(now())
  updatedAt   DateTime           @updatedAt
}

// ==================== ENERGY CONTRACT DETAILS ====================

enum InvoiceType {
  INTERIM        // Zwischenrechnung
  FINAL          // Schlussrechnung
  NOT_AVAILABLE  // Rechnung nicht mehr zu bekommen
}

model EnergyContractDetails {
  id                     Int       @id @default(autoincrement())
  contractId             Int       @unique
  contract               Contract  @relation(fields: [contractId], references: [id], onDelete: Cascade)
  meterId                Int?
  meter                  Meter?    @relation(fields: [meterId], references: [id])
  maloId                 String?   // Marktlokations-ID
  annualConsumption      Float?    // kWh für Strom, m³ für Gas
  annualConsumptionKwh   Float?    // kWh für Gas (zusätzlich zu m³)
  basePrice              Float?    // €/Monat
  unitPrice              Float?    // €/kWh (Arbeitspreis) - bei HT/NT: HT-Preis
  unitPriceNt            Float?    // €/kWh NT-Preis (nur bei Zweitarifzähler)
  // Bonus wurde 2026-05-24 in zwei Felder aufgeteilt: Sofort-Bonus
  // (Auszahlung kurz nach Wechsel) + Neukunden-Bonus (Auszahlung am
  // Vertragsende / nach 12 Monaten). Beide werden im Detail als
  // Gesamtbonus aufsummiert und in der Kostenvorschau einzeln
  // dargestellt. Migration kopiert das alte `bonus` nach
  // `instantBonus` (Annahme: meistgenutzte Variante).
  instantBonus           Float?    // Sofort-Bonus
  newCustomerBonus       Float?    // Neukunden-Bonus
  noBonusDesired         Boolean   @default(false) // Kunde wünscht keine Bonis
  previousProviderName   String?
  previousCustomerNumber String?
  invoices               Invoice[] // Rechnungen
  contractMeters         ContractMeter[] // Zähler-Zuordnungen (inkl. Folgezähler)
}

model ContractMeter {
  id                      Int                   @id @default(autoincrement())
  energyContractDetailsId Int
  energyContractDetails   EnergyContractDetails @relation(fields: [energyContractDetailsId], references: [id], onDelete: Cascade)
  meterId                 Int
  meter                   Meter                 @relation("ContractMeters", fields: [meterId], references: [id])
  position                Int                   @default(0)    // 0 = Original, 1 = erster Folgezähler, etc.
  installedAt             DateTime?              // Ab wann wird dieser Zähler am Vertrag genutzt?
  removedAt               DateTime?              // Wann wurde der Zähler gewechselt? (null = aktuell)
  finalReading            Float?                 // Letzter Stand vor dem Wechsel
  createdAt               DateTime              @default(now())

  @@unique([energyContractDetailsId, meterId])
  @@index([energyContractDetailsId])
}

model Invoice {
  id                      Int                   @id @default(autoincrement())
  energyContractDetailsId Int?
  energyContractDetails   EnergyContractDetails? @relation(fields: [energyContractDetailsId], references: [id], onDelete: Cascade)
  contractId              Int?
  contract                Contract?             @relation(fields: [contractId], references: [id], onDelete: Cascade)
  invoiceDate             DateTime
  invoiceType             InvoiceType
  documentPath            String?               // Pflicht, außer bei NOT_AVAILABLE
  notes                   String?
  createdAt               DateTime              @default(now())
  updatedAt               DateTime              @updatedAt

  @@index([energyContractDetailsId])
  @@index([contractId])
}

// ==================== GUTSCHRIFTEN (CREDIT NOTES / SUBVENTIONEN) ====================

// Art der Gutschrift: Geldbetrag (per Überweisung, in EUR) oder Sachwert
// (Smartphone, Elektrogerät …, näher im Textfeld beschrieben).
enum CreditNoteType {
  GELD
  SACHWERT
}

// Kundentyp zum Zeitpunkt der Gutschrift – bestimmt die USt-Sicht
// (Privat rechnet brutto, Firma zieht Vorsteuer). Wird aus dem Kunden
// vorbelegt, ist aber pro Gutschrift überschreibbar.
enum CreditNoteCustomerType {
  PRIVAT
  FIRMA
}

// Basis, auf die sich der eingegebene Betrag bezieht (nur relevant, wenn
// vatRelevant = true). Bei NETTO wird die USt aufgeschlagen, bei BRUTTO
// herausgerechnet.
enum CreditNoteAmountBasis {
  NETTO
  BRUTTO
}

model CreditNote {
  id                  Int                    @id @default(autoincrement())
  contractId          Int
  contract            Contract               @relation(fields: [contractId], references: [id], onDelete: Cascade)

  // Gutschrift-Nummer aus dem Gutschrift-Nummernkreis (z.B. GS-2026-0001).
  // NULL bei betragslosen Sachwert-Übergaben (= Lieferschein, s.u.).
  number              String?                @unique
  // Lieferschein-/Subventionsnummer aus dem separaten Lieferschein-
  // Nummernkreis (z.B. LS-2026-0001) – nur bei betragslosen Sachwerten.
  // Beide Nummern werden lazy pro Serie vergeben und NIE wieder freigegeben
  // (keine Lücken); angezeigt wird je nach aktuellem Typ die passende.
  deliveryNoteNumber  String?                @unique

  type                CreditNoteType
  // Pflicht bei SACHWERT: was wird gewährt (z.B. "Smartphone Samsung A55").
  sachwertDescription String?

  customerType        CreditNoteCustomerType
  // USt-relevant ja/nein (Steuerberater-Auslegung: Vermittlungsleistung vs.
  // reiner Abschlussbonus/Entgeltminderung). Bei false: net = brutto, USt = 0.
  vatRelevant         Boolean                @default(false)
  amountBasis         CreditNoteAmountBasis  @default(BRUTTO)
  vatRate             Float                  @default(19) // Prozent
  amountNet           Float                  @default(0)
  amountVat           Float                  @default(0)
  amountGross         Float                  @default(0)
  currency            String                 @default("EUR")

  // Datum der Gutschrift (frei änderbar).
  creditDate          DateTime
  // Ort der Unterschrift + Unterschrift-/Bestätigungszeitpunkt.
  place               String?
  signedAt            DateTime?
  // Sachwert: Bestätigung "Ware erhalten".
  goodsReceived       Boolean                @default(false)

  // Auszahlungskonto (nur GELD): Bankkarte des Kunden, auf die überwiesen
  // wird. Kann ein anderes Konto sein als das Abbuchkonto des Vertrags.
  // ON DELETE SET NULL, damit das Löschen einer Bankkarte die Gutschrift
  // (historischer Beleg) nicht mitreißt.
  payoutBankCardId    Int?
  payoutBankCard      BankCard?              @relation("CreditNotePayoutBankCard", fields: [payoutBankCardId], references: [id], onDelete: SetNull)

  // Überweisungsbeleg (nur GELD) – hochgeladene Datei.
  receiptPath         String?
  // Generiertes Gutschrift-Dokument (PDF bzw. ZUGFeRD-Hybrid) – Phase 3.
  pdfPath             String?

  notes               String?
  createdBy           String?
  createdAt           DateTime               @default(now())
  updatedAt           DateTime               @updatedAt

  @@index([contractId])
}

// Absender-/Firmenstammdaten (Einzel-Zeile). Fließen in Gutschrift-PDF und
// ZUGFeRD-XML (Verkäufer/Seller-Party) sowie in die Bankangaben ein.
model CompanyProfile {
  id                 Int      @id @default(autoincrement())
  name               String   @default("")
  street             String   @default("")
  houseNumber        String   @default("")
  postalCode         String   @default("")
  city               String   @default("")
  country            String   @default("DE") // ISO-2, für ZUGFeRD countryID
  vatId              String   @default("")   // USt-IdNr (DE...)
  taxNumber          String   @default("")   // Steuernummer
  commercialRegister String   @default("")   // z.B. HRB 12345, Amtsgericht
  managingDirector   String   @default("")   // Geschäftsführer/Inhaber
  email              String   @default("")
  phone              String   @default("")
  website            String   @default("")
  iban               String   @default("")
  bic                String   @default("")
  bankName           String   @default("")
  logoPath           String?
  updatedAt          DateTime @updatedAt
}

// Nummernkreis für Gutschriften (Einzel-Zeile, in den Einstellungen
// verwaltbar). Nummer wird transaktional vergeben, damit keine Lücken/
// Doppelvergaben entstehen.
model CreditNoteNumberRange {
  id           Int      @id @default(autoincrement())
  prefix       String   @default("GS-")
  includeYear  Boolean  @default(true)
  separator    String   @default("-")
  padding      Int      @default(4)
  nextNumber   Int      @default(1)
  resetYearly  Boolean  @default(true)
  currentYear  Int?
  updatedAt    DateTime @updatedAt
}

// Separater Nummernkreis für Lieferscheine (betragslose Sachwert-Übergaben).
// Eigene Serie, damit die Gutschrift-Nummerierung sauber/lückenlos bleibt.
model DeliveryNoteNumberRange {
  id           Int      @id @default(autoincrement())
  prefix       String   @default("LS-")
  includeYear  Boolean  @default(true)
  separator    String   @default("-")
  padding      Int      @default(4)
  nextNumber   Int      @default(1)
  resetYearly  Boolean  @default(true)
  currentYear  Int?
  updatedAt    DateTime @updatedAt
}

// ==================== INTERNET CONTRACT DETAILS ====================

model InternetContractDetails {
  id                        Int           @id @default(autoincrement())
  contractId                Int           @unique
  contract                  Contract      @relation(fields: [contractId], references: [id], onDelete: Cascade)
  downloadSpeed             Int?
  uploadSpeed               Int?
  routerModel               String?
  routerSerialNumber        String?
  installationDate          DateTime?
  // Internet-Zugangsdaten
  internetUsername          String?
  internetPasswordEncrypted String?       // Verschlüsselt gespeichert
  // Objekt & Lage
  propertyType              String?       // Objekttyp (Mehrparteienhaus, Freistehendes Haus, etc.)
  propertyLocation          String?       // Lage (Erdgeschoss, OG1, etc.)
  connectionLocation        String?       // Lage des Anschlusses (Flur, HWR, etc.)
  // Glasfaser-spezifisch
  homeId                    String?
  // Vodafone DSL/Kabel spezifisch
  activationCode            String?
  phoneNumbers              PhoneNumber[]
}

model PhoneNumber {
  id                        Int                     @id @default(autoincrement())
  internetContractDetailsId Int
  internetDetails           InternetContractDetails @relation(fields: [internetContractDetailsId], references: [id], onDelete: Cascade)
  // phoneNumber bleibt die komplette Nummer (Vorwahl + Anschluss) für
  // bestehende Reports/Listen. Vorwahl wird zusätzlich getrennt erfasst,
  // damit Auftragsformulare (Wechselauftrag PDF) sie verlässlich
  // befüllen können – ohne heuristischen String-Split.
  phoneNumber               String
  areaCode                  String?
  isMain                    Boolean                 @default(false)
  // SIP-Zugangsdaten
  sipUsername               String?
  sipPasswordEncrypted      String?                 // Verschlüsselt gespeichert
  sipServer                 String?
}

// ==================== MOBILE CONTRACT DETAILS ====================

model MobileContractDetails {
  id              Int       @id @default(autoincrement())
  contractId      Int       @unique
  contract        Contract  @relation(fields: [contractId], references: [id], onDelete: Cascade)
  requiresMultisim Boolean  @default(false)  // Multisim erforderlich?
  // Physisches Mobilfunknetz, auf dem der Tarif läuft: TELEKOM | VODAFONE
  // | TELEFONICA (null = nicht gewählt). Als String statt Enum gehalten,
  // damit weitere Netze ohne Migration ergänzbar sind.
  mobileNetwork   String?
  dataVolume      Float?
  includedMinutes Int?
  includedSMS     Int?
  deviceModel     String?
  deviceImei      String?
  simCards        SimCard[]
  // Legacy-Felder (für Abwärtskompatibilität, werden durch simCards ersetzt)
  phoneNumber     String?
  simCardNumber   String?
}

model SimCard {
  id              Int                   @id @default(autoincrement())
  mobileDetailsId Int
  mobileDetails   MobileContractDetails @relation(fields: [mobileDetailsId], references: [id], onDelete: Cascade)
  phoneNumber     String?               // Rufnummer
  simCardNumber   String?               // SIM-Kartennummer
  pin             String?               // PIN (verschlüsselt gespeichert)
  puk             String?               // PUK (verschlüsselt gespeichert)
  isMultisim      Boolean               @default(false)  // Ist dies eine Multisim-Karte?
  isMain          Boolean               @default(false)  // Ist dies die Hauptkarte?
  isEsim          Boolean               @default(false)  // Ist dies eine eSIM?
  // Tatsächlicher Nutzer der SIM-Karte (z.B. Mitarbeiter bei Firmenverträgen,
  // Kind bei Eltern-Vertrag) – kann vom Vertragsinhaber abweichen.
  cardUser        String?
  createdAt       DateTime              @default(now())
  updatedAt       DateTime              @updatedAt
}

// ==================== TV CONTRACT DETAILS ====================

model TvContractDetails {
  id             Int      @id @default(autoincrement())
  contractId     Int      @unique
  contract       Contract @relation(fields: [contractId], references: [id], onDelete: Cascade)
  receiverModel  String?
  smartcardNumber String?
  package        String?
}

// ==================== CAR INSURANCE DETAILS ====================

enum InsuranceType {
  LIABILITY
  PARTIAL
  FULL
}

model CarInsuranceDetails {
  id                Int           @id @default(autoincrement())
  contractId        Int           @unique
  contract          Contract      @relation(fields: [contractId], references: [id], onDelete: Cascade)
  licensePlate      String?
  hsn               String?
  tsn               String?
  vin               String?
  vehicleType       String?
  firstRegistration DateTime?
  noClaimsClass     String?
  insuranceType     InsuranceType @default(LIABILITY)
  deductiblePartial Float?
  deductibleFull    Float?
  policyNumber      String?
  previousInsurer   String?
}

// ==================== AUDIT LOGGING (DSGVO) ====================

/// Einzeiliger Mutex fuer die Audit-Hash-Kette (genau eine Zeile, id = 1).
///
/// Warum eine eigene Tabelle statt GET_LOCK oder `SELECT … FOR UPDATE` auf
/// AuditLog selbst:
///  - GET_LOCK muss auf derselben Verbindung freigegeben werden. Innerhalb des
///    Prisma-Transaktions-Callbacks faellt das Release damit VOR den COMMIT –
///    der naechste Schreiber liest das Kettenende, bevor die Vorgaengerzeile
///    sichtbar ist, und haengt sich an denselben Vorgaenger (Fork, R166-01).
///  - `FOR UPDATE` auf das Kettenende von AuditLog nimmt Gap-/Next-Key-Locks,
///    die mit den gleichzeitigen INSERTs kollidieren (Deadlocks, dabei gingen
///    38 von 40 Eintraegen verloren).
/// InnoDB-Zeilensperren werden erst beim COMMIT freigegeben – genau das
/// schliesst das Fenster.
model AuditChainLock {
  id        Int      @id
  updatedAt DateTime @updatedAt
}

enum AuditAction {
  CREATE
  READ
  UPDATE
  DELETE
  EXPORT        // DSGVO-Datenexport
  ANONYMIZE     // Recht auf Vergessenwerden
  LOGIN
  LOGOUT
  LOGIN_FAILED
  TOKEN_REFRESH // stiller Access-Token-Refresh (Cookie), kein interaktiver Login
}

enum AuditSensitivity {
  LOW           // Einstellungen, Plattformen
  MEDIUM        // Verträge, Tarife
  HIGH          // Kundendaten, Bankdaten
  CRITICAL      // Authentifizierung, Ausweisdokumente
}

model AuditLog {
  id               Int              @id @default(autoincrement())

  // Wer
  userId           Int?             // Staff User (null bei Kundenportal/System)
  userEmail        String
  userRole         String?          @db.Text  // Rolle zum Zeitpunkt der Aktion
  customerId       Int?             // Bei Kundenportal-Zugriff
  isCustomerPortal Boolean          @default(false)

  // Was
  action           AuditAction
  sensitivity      AuditSensitivity @default(MEDIUM)

  // Welche Ressource
  resourceType     String           // Prisma Model Name
  resourceId       String?          // ID des Datensatzes
  resourceLabel    String?          // Lesbare Bezeichnung

  // Kontext
  endpoint         String           // API-Pfad
  httpMethod       String           // GET, POST, PUT, DELETE
  ipAddress        String
  userAgent        String?          @db.Text

  // Änderungen (JSON, bei sensiblen Daten verschlüsselt)
  changesBefore    String?          @db.LongText
  changesAfter     String?          @db.LongText
  changesEncrypted Boolean          @default(false)

  // DSGVO
  dataSubjectId    Int?             // Betroffene Person (für Reports)
  legalBasis       String?          // Rechtsgrundlage

  // Status
  success          Boolean          @default(true)
  errorMessage     String?          @db.Text
  durationMs       Int?

  // Unveränderlichkeit (Hash-Kette)
  createdAt        DateTime         @default(now())
  hash             String?          // SHA-256 Hash des Eintrags
  previousHash     String?          // Hash des vorherigen Eintrags
  /// 1 = Alt-Hash ueber 7 Felder, 2 = Hash ueber alle Inhaltsspalten
  /// (Pentest R166-02). Bestandszeilen bleiben mit Version 1 gueltig.
  hashVersion      Int              @default(1)

  @@index([userId])
  @@index([customerId])
  @@index([resourceType, resourceId])
  @@index([dataSubjectId])
  @@index([action])
  @@index([createdAt])
  @@index([sensitivity])
}

// ==================== CONSENT MANAGEMENT (DSGVO) ====================

enum ConsentType {
  DATA_PROCESSING       // Grundlegende Datenverarbeitung
  MARKETING_EMAIL       // E-Mail-Marketing
  MARKETING_PHONE       // Telefon-Marketing
  DATA_SHARING_PARTNER  // Weitergabe an Partner
}

enum ConsentStatus {
  GRANTED
  WITHDRAWN
  PENDING
}

model CustomerConsent {
  id           Int           @id @default(autoincrement())
  customerId   Int
  customer     Customer      @relation(fields: [customerId], references: [id], onDelete: Cascade)

  consentType  ConsentType
  status       ConsentStatus @default(PENDING)

  grantedAt    DateTime?
  withdrawnAt  DateTime?
  source       String?       // "portal", "telefon", "papier", "email"
  documentPath String?       // Unterschriebenes Dokument
  version      String?       // Version der Datenschutzerklärung
  ipAddress    String?

  createdBy    String        // User der die Einwilligung erfasst hat
  createdAt    DateTime      @default(now())
  updatedAt    DateTime      @updatedAt

  @@unique([customerId, consentType])
  @@index([customerId])
  @@index([consentType])
  @@index([status])
}

// ==================== DATA DELETION REQUESTS (DSGVO) ====================

enum DeletionRequestStatus {
  PENDING             // Anfrage eingegangen
  IN_PROGRESS         // Wird bearbeitet
  COMPLETED           // Abgeschlossen
  PARTIALLY_COMPLETED // Teildaten behalten (rechtliche Gründe)
  REJECTED            // Abgelehnt
}

model DataDeletionRequest {
  id              Int                   @id @default(autoincrement())
  customerId      Int

  status          DeletionRequestStatus @default(PENDING)
  requestedAt     DateTime              @default(now())
  requestSource   String                // "email", "portal", "brief"
  requestedBy     String                // Wer hat angefragt

  processedAt     DateTime?
  processedBy     String?               // Mitarbeiter der bearbeitet hat

  deletedData     String?               @db.LongText  // JSON: Was wurde gelöscht
  retainedData    String?               @db.LongText  // JSON: Was wurde behalten + Grund
  retentionReason String?               @db.Text      // Begründung für Aufbewahrung

  proofDocument   String?               // Pfad zum Löschnachweis-PDF

  createdAt       DateTime              @default(now())
  updatedAt       DateTime              @updatedAt

  @@index([customerId])
  @@index([status])
  @@index([requestedAt])
}

// ==================== AUDIT RETENTION POLICIES ====================

model AuditRetentionPolicy {
  id            Int               @id @default(autoincrement())
  resourceType  String            // "*" für Standard, oder spezifischer Model-Name
  sensitivity   AuditSensitivity?
  retentionDays Int               // Aufbewahrungsfrist in Tagen (z.B. 3650 = 10 Jahre)
  description   String?
  legalBasis    String?           // Gesetzliche Grundlage (z.B. "AO §147", "HGB §257")
  isActive      Boolean           @default(true)

  createdAt     DateTime          @default(now())
  updatedAt     DateTime          @updatedAt

  @@unique([resourceType, sensitivity])
}

// ==================== SECURITY MONITORING ====================
// Sicherheitsrelevante Events für Realtime-Alerting + Forensik.
// Im Gegensatz zum AuditLog (forensisch, hash-gekettet) ist das hier
// optimiert für schnelles Filtern + Alerting (nicht-tamper-evident, dafür
// effizient querybar). Threshold-Detection läuft per Cron.

enum SecurityEventType {
  LOGIN_FAILED            // falsches Passwort / unbekannter User
  LOGIN_SUCCESS           // erfolgreicher Login (informativ)
  RATE_LIMIT_HIT          // express-rate-limit hat zugeschlagen
  ACCESS_DENIED           // 403 von canAccess* (versuchter IDOR)
  SSRF_BLOCKED            // ssrfGuard hat geblockte Adresse abgefangen
  PASSWORD_RESET_REQUEST  // Reset-Mail angefordert
  PASSWORD_RESET_CONFIRM  // Reset abgeschlossen
  LOGOUT                  // expliziter Logout
  TOKEN_REJECTED          // ungültiger / abgelaufener / manipulierter JWT
  PERMISSION_CHANGED      // Admin hat Rolle/Permission geändert
  SUSPICIOUS              // generischer Catch-All
}

enum SecuritySeverity {
  INFO      // Login-Success, Logout
  LOW       // Einzelner failed Login, einzelner 403
  MEDIUM    // Rate-Limit-Hit, mehrere 403er
  HIGH      // SSRF-Block, JWT-Manipulation
  CRITICAL  // Threshold überschritten (>10 failed login/h, >5 403/min)
}

enum BackupOperation {
  CREATE
  RESTORE
}

// Persistiertes Log für Backup-Vorgänge.
// `summary` ist die einzeilige Anzeige in der Liste (z.B. "4859 Datensätze
// wiederhergestellt"), `fullLog` der detaillierte Output inkl. Stack-Trace
// für das Modal. Wird beim Build/Restore in `backup.controller.ts`
// geschrieben.
model BackupLog {
  id          Int             @id @default(autoincrement())
  operation   BackupOperation
  backupName  String?
  success     Boolean
  durationMs  Int             @default(0)
  summary     String          @db.Text
  fullLog     String          @db.LongText
  userId      Int?
  userEmail   String?
  ipAddress   String?
  createdAt   DateTime        @default(now())

  @@index([operation, createdAt])
  @@index([createdAt])
}

model SecurityEvent {
  id          Int                @id @default(autoincrement())
  type        SecurityEventType
  severity    SecuritySeverity
  message     String             @db.Text
  ipAddress   String?
  userId      Int?               // Mitarbeiter (falls eingeloggt)
  customerId  Int?               // Portal-Kunde (falls eingeloggt)
  userEmail   String?            // beste Schätzung – auch bei nicht eingeloggt
  endpoint    String?            // betroffener Endpoint
  details     Json?              // strukturierte Zusatzinfo
  alerted     Boolean            @default(false)  // schon per Email versendet?
  alertedAt   DateTime?

  createdAt   DateTime           @default(now())

  @@index([type, createdAt])
  @@index([severity, createdAt])
  @@index([ipAddress, createdAt])
  @@index([alerted, severity])
}
