import { Injectable } from '@nestjs/common'
import { InjectModel } from '@nestjs/mongoose'
import { Model, Types } from 'mongoose'
import {
  ClientFolder, ClientFolderDocument,
  Matter, MatterDocument,
  Email, EmailDocument,
  AssociationCandidate,
} from '../common/schemas'
import { EncryptionService } from '../encryption/encryption.service'

@Injectable()
export class EmailAssociationService {
  constructor(
    @InjectModel(ClientFolder.name) private folderModel:  Model<ClientFolderDocument>,
    @InjectModel(Matter.name)       private matterModel:  Model<MatterDocument>,
    @InjectModel(Email.name)        private emailModel:   Model<EmailDocument>,
    private readonly enc:           EncryptionService,
  ) {}

  /**
   * Main entry point — given a parsed email, attempt to associate it with
   * a client folder and optionally a matter.
   *
   * Priority order:
   *   1. Thread inheritance — if an earlier email in the thread is already
   *      confirmed/auto_matched, inherit that association immediately.
   *   2. Email address match — exact match against folder.email or emails[].address
   *   3. Display name match — fuzzy search against folder.displayName
   *   4. Open matter scan — check if any open matter has a party with this address
   */
  async associate(email: Partial<EmailDocument>): Promise<{
    clientFolderId?: string
    matterId?: string
    associationStatus: string
    associationCandidates: AssociationCandidate[]
  }> {
    // ── 1. Thread inheritance ──────────────────────────────────────────────
    if (email.threadId && email.threadId !== email.messageId) {
      const prior = await this.emailModel
        .findOne({
          threadId: email.threadId,
          associationStatus: { $in: ['auto_matched', 'confirmed'] },
          clientFolderId: { $exists: true },
        })
        .sort({ receivedAt: -1 })
        .lean()
        .exec()

      if (prior?.clientFolderId) {
        return {
          clientFolderId: prior.clientFolderId,
          matterId:        prior.matterId,
          associationStatus: 'auto_matched',
          associationCandidates: [],
        }
      }
    }

    // Collect all external email addresses
    const externalAddresses = this.extractAddresses(email)

    // ── 2. Email address match ─────────────────────────────────────────────
    const addressCandidates = await this.matchByEmailAddress(externalAddresses)
    if (addressCandidates.length === 1) {
      // Single unambiguous match — auto-associate
      const c = addressCandidates[0]
      const matter = await this.pickOpenMatter(c.clientFolderId)
      return {
        clientFolderId: c.clientFolderId,
        matterId: matter?._id?.toString(),
        associationStatus: matter ? 'auto_matched' : 'pending_review',
        associationCandidates: matter
          ? []
          : await this.buildMatterCandidates(c.clientFolderId),
      }
    }

    if (addressCandidates.length > 1) {
      return {
        associationStatus: 'pending_review',
        associationCandidates: addressCandidates,
      }
    }

    // ── 3. Display name match ──────────────────────────────────────────────
    const nameCandidates = await this.matchByDisplayName(externalAddresses)
    if (nameCandidates.length > 0) {
      return {
        associationStatus: 'pending_review',
        associationCandidates: nameCandidates,
      }
    }

    // ── 4. Nothing matched ─────────────────────────────────────────────────
    return {
      associationStatus: 'unassociated',
      associationCandidates: [],
    }
  }

  // ── Helpers ──────────────────────────────────────────────────────────────

  private extractAddresses(email: Partial<EmailDocument>): string[] {
    const addrs: string[] = []
    const push = (list?: { address: string }[]) =>
      list?.forEach(a => { if (a.address) addrs.push(a.address.toLowerCase().trim()) })
    push([email.from as { address: string }])
    push(email.to)
    push(email.cc)
    // deduplicate
    return [...new Set(addrs)]
  }

  private async matchByEmailAddress(addresses: string[]): Promise<AssociationCandidate[]> {
    if (!addresses.length) return []

    // Search via keyword hash index — works with encrypted PII fields
    const hashes = addresses.map(a => this.enc.hash(a))

    const folders = await this.folderModel.find({
      keywords: { $in: hashes },
    }).lean().exec()

    return folders.map(f => ({
      clientFolderId: String(f._id),
      displayName:    this.enc.decrypt(f.displayName),
      folderNumber:   f.folderNumber,
      confidence:     0.95,
      reason:         'email_match',
    }))
  }

  private async matchByDisplayName(addresses: string[]): Promise<AssociationCandidate[]> {
    // Extract display names from address local-part e.g. "john.smith" → ["john", "smith"]
    const nameTokens = addresses.flatMap(a =>
      a.split('@')[0].replace(/[._+-]/g, ' ').trim().split(/\s+/).filter(t => t.length > 2),
    )
    if (!nameTokens.length) return []

    const tokenHashes = nameTokens.map(t => this.enc.hash(t))

    const folders = await this.folderModel.find({
      keywords: { $in: tokenHashes },
    }).limit(5).lean().exec()

    return folders.map(f => ({
      clientFolderId: String(f._id),
      displayName:    this.enc.decrypt(f.displayName),
      folderNumber:   f.folderNumber,
      confidence:     0.5,
      reason:         'name_match',
    }))
  }

  private async pickOpenMatter(clientFolderId: string): Promise<MatterDocument | null> {
    const matters = await this.matterModel.find({
      clientFolderId: new Types.ObjectId(clientFolderId),
      status: 'open',
    }).lean().exec()

    if (matters.length === 1) return matters[0] as unknown as MatterDocument
    return null
  }

  private async buildMatterCandidates(clientFolderId: string): Promise<AssociationCandidate[]> {
    const folder = await this.folderModel.findById(clientFolderId).lean().exec()
    const matters = await this.matterModel.find({
      clientFolderId: new Types.ObjectId(clientFolderId),
      status: { $in: ['open', 'pending'] },
    }).lean().exec()

    return matters.map(m => ({
      clientFolderId,
      displayName:  folder?.displayName ?? '',
      folderNumber: folder?.folderNumber ?? '',
      matterId:     String(m._id),
      matterNumber: m.matterNumber,
      matterTitle:  m.title,
      confidence:   0.85,
      reason:       'open_matter',
    }))
  }
}
