import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'
import { InjectModel } from '@nestjs/mongoose'
import { Model, Types } from 'mongoose'
import { promises as fs } from 'fs'
import * as path from 'path'
import { ClientFolder, ClientFolderDocument, ConflictCheck, ConflictCheckDocument, LegalDocument, LegalDocumentDocument } from '../common/schemas'
import { CreateClientFolderDto } from './dto/create-client-folder.dto'
import { UpdateClientFolderDto } from './dto/update-client-folder.dto'
import { EncryptionService } from '../encryption/encryption.service'

// Default document folders created for every new client
const DEFAULT_DOC_FOLDERS = [
  { name: 'Correspondence',  icon: 'ri-mail-line',          color: '#4A90D9' },
  { name: 'Contracts',       icon: 'ri-file-text-line',     color: '#7B68EE' },
  { name: 'Court Filings',   icon: 'ri-scales-line',        color: '#E8533A' },
  { name: 'Identity',        icon: 'ri-user-line',          color: '#2ECC71' },
  { name: 'Financial',       icon: 'ri-bank-line',          color: '#F39C12' },
  { name: 'Reports',         icon: 'ri-bar-chart-line',     color: '#1ABC9C' },
  { name: 'Evidence',        icon: 'ri-camera-line',        color: '#E91E63' },
  { name: 'Misc',            icon: 'ri-folder-line',        color: '#95A5A6' },
]

@Injectable()
export class ClientFoldersService {
  constructor(
    @InjectModel(ClientFolder.name)  private folderModel:   Model<ClientFolderDocument>,
    @InjectModel(ConflictCheck.name) private conflictModel: Model<ConflictCheckDocument>,
    @InjectModel(LegalDocument.name) private docModel:      Model<LegalDocumentDocument>,
    private readonly enc: EncryptionService,
  ) {}

  async create(dto: CreateClientFolderDto): Promise<ClientFolderDocument> {
    const folderNumber = await this.generateFolderNumber()
    const displayName = dto.clientType === 'individual'
      ? `${dto.lastName}, ${dto.firstName}`
      : dto.companyName!

    const now = new Date().toISOString()
    const docFolders = DEFAULT_DOC_FOLDERS.map((f, i) => ({
      _id: new Types.ObjectId().toString(),
      name: f.name,
      icon: f.icon,
      color: f.color,
      isSystem: true,
      createdAt: now,
      createdBy: dto.originatingAttorney,
    }))

    const folder = new this.folderModel({ ...dto, folderNumber, displayName, status: 'prospect', docFolders, batesCounter: 0 })
    return folder.save()
  }

  async findAll(filters: {
    status?: string
    attorney?: string
    search?: string
    limit?: number
    offset?: number
  }): Promise<{ items: ClientFolderDocument[]; total: number }> {
    const query: any = { archived: false }
    if (filters.status) query.status = filters.status
    if (filters.attorney) query.$or = [
      { responsibleAttorney: filters.attorney },
      { originatingAttorney: filters.attorney },
    ]
    if (filters.search) {
      const term = filters.search.trim()
      // Check folderNumber first (unencrypted) — quick prefix match
      const folderRe = new RegExp(term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i')
      const tokenHashes = this.enc.tokenHashes(term)
      query.$or = [
        { folderNumber: folderRe },
        ...(tokenHashes.length ? [{ keywords: { $in: tokenHashes } }] : []),
      ]
    }

    const [items, total] = await Promise.all([
      this.folderModel.find(query)
        .sort({ createdAt: -1 })
        .skip(filters.offset ?? 0)
        .limit(filters.limit ?? 50)
        .exec(),
      this.folderModel.countDocuments(query),
    ])
    return { items, total }
  }

  async findOne(id: string): Promise<ClientFolderDocument> {
    const folder = await this.folderModel.findById(id).exec()
    if (!folder) throw new NotFoundException(`Client folder ${id} not found`)
    return folder
  }

  async findByFolderNumber(folderNumber: string): Promise<ClientFolderDocument> {
    const folder = await this.folderModel.findOne({ folderNumber }).exec()
    if (!folder) throw new NotFoundException(`Folder ${folderNumber} not found`)
    return folder
  }

  async update(id: string, dto: UpdateClientFolderDto): Promise<ClientFolderDocument> {
    const folder = await this.folderModel.findByIdAndUpdate(id, dto, { new: true }).exec()
    if (!folder) throw new NotFoundException(`Client folder ${id} not found`)
    return folder as ClientFolderDocument
  }

  async activate(id: string, engagementLetterRef: string): Promise<ClientFolderDocument> {
    const folder = await this.findOne(id)
    if (folder.status !== 'conflict_check') {
      throw new BadRequestException('Folder must pass conflict check before activation')
    }
    const check = await this.conflictModel.findOne({ clientFolderId: id, result: 'clear' }).exec()
      || await this.conflictModel.findOne({ clientFolderId: id, result: 'waived' }).exec()
    if (!check) {
      throw new BadRequestException('No cleared or waived conflict check on record')
    }
    return this.folderModel.findByIdAndUpdate(
      id,
      { status: 'active', engagementDate: new Date(), engagementLetterRef },
      { new: true },
    ).exec() as Promise<ClientFolderDocument>
  }

  async close(id: string, outcome: string): Promise<ClientFolderDocument> {
    return this.folderModel.findByIdAndUpdate(
      id,
      { status: 'closed', closedDate: new Date(), closedReason: outcome },
      { new: true },
    ).exec() as Promise<ClientFolderDocument>
  }

  // Directly converts a prospect to an active client — skips formal conflict-check workflow.
  // Suitable for small firms where the attorney has already verified conflicts informally.
  async convertToClient(id: string, engagementLetterRef: string): Promise<ClientFolderDocument> {
    const folder = await this.findOne(id)
    if (folder.status !== 'prospect') {
      throw new BadRequestException('Only prospect folders can be converted directly to active')
    }
    return this.folderModel.findByIdAndUpdate(
      id,
      { status: 'active', engagementDate: new Date(), engagementLetterRef },
      { new: true },
    ).exec() as Promise<ClientFolderDocument>
  }

  async searchForConflicts(searchTerms: string[]): Promise<ClientFolderDocument[]> {
    // Hash each search term and look up in keyword index (works with encrypted PII)
    const tokenHashes = searchTerms.flatMap(t => this.enc.tokenHashes(t))
    if (!tokenHashes.length) return []
    return this.folderModel.find({ keywords: { $in: tokenHashes } }).exec()
  }

  async uploadDocuments(
    folderId: string,
    files: Express.Multer.File[],
    uploadedBy: string,
  ): Promise<LegalDocumentDocument[]> {
    const folder = await this.findOne(folderId)
    const results: LegalDocumentDocument[] = []

    for (const file of files) {
      // Atomically increment batesCounter to get the sequence number for this doc
      const updated = await this.folderModel.findByIdAndUpdate(
        folderId,
        { $inc: { batesCounter: 1 } },
        { new: true },
      ).exec()
      const seq = updated!.batesCounter
      const batesNumber = `${folder.folderNumber}-${String(seq).padStart(5, '0')}`

      // Persist file to local uploads directory
      const ext = path.extname(file.originalname)
      const storageKey = path.join(folderId, `${batesNumber}${ext}`)
      const uploadsRoot = process.env.UPLOADS_DIR ?? path.join(process.cwd(), 'uploads')
      const dest = path.join(uploadsRoot, storageKey)
      await fs.mkdir(path.dirname(dest), { recursive: true })
      await fs.writeFile(dest, file.buffer)

      // Create document record
      const doc = await this.docModel.create({
        clientFolderId: new Types.ObjectId(folderId),
        batesNumber,
        originalName: file.originalname,
        fileName: `${batesNumber}${ext}`,
        storageKey,
        mimeType: file.mimetype,
        fileSize: file.size,
        title: file.originalname,
        aiStatus: 'pending',
        privilege: 'non_privileged',
        classified: false,
        parties: [],
        tags: [],
        uploadedBy,
      })
      results.push(doc)

      // Notify client-manager async (fire and forget — don't block the upload response)
      this.notifyClientManager({
        documentId: doc._id.toString(),
        clientFolderId: folderId,
        storageKey,
        mimeType: file.mimetype,
        fileName: file.originalname,
        batesNumber,
        uploadedBy,
        contentBase64: file.buffer.toString('base64'),
        clientName: folder.displayName,
        folderNumber: folder.folderNumber,
        availableFolders: folder.docFolders?.map(f => f.name) ?? [],
      }).catch(err => console.error(`[upload] client-manager notify failed for ${batesNumber}:`, err))
    }

    return results
  }

  private async notifyClientManager(event: Record<string, unknown>): Promise<void> {
    const url = process.env.CLIENT_MANAGER_URL ?? 'http://localhost:3301'
    const res = await fetch(`${url}/document-uploaded`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(event),
    })
    if (!res.ok) {
      console.error(`[upload] client-manager returned ${res.status}`)
    }
  }

  private async generateFolderNumber(): Promise<string> {
    const year = new Date().getFullYear()
    const count = await this.folderModel.countDocuments({
      folderNumber: new RegExp(`^CF-${year}-`),
    })
    return `CF-${year}-${String(count + 1).padStart(4, '0')}`
  }
}
