import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'
import { InjectModel } from '@nestjs/mongoose'
import { Model, Types } from 'mongoose'
import {
  MatterDeadline, MatterDeadlineDocument,
  ObligationRule, ObligationRuleDocument,
  Matter, MatterDocument,
  ClientFolder, ClientFolderDocument,
  DeadlineStatus,
} from '../common/schemas'
import { STANDARD_OBLIGATION_RULES } from './obligation-rules.seed'

// ─── Anchor resolver ──────────────────────────────────────────────────────────
// Resolves "client.arrivalDate" → actual Date value from client/matter docs

function resolveAnchor(
  field: string,
  client: any,
  matter: any,
): Date | null {
  const parts = field.split('.')
  const ns = parts[0]
  const rest = parts.slice(1)

  // Walk the nested path on the root object
  let raw: any = ns === 'client' ? client : ns === 'matter' ? matter : null
  for (const key of rest) {
    if (raw == null) return null
    raw = raw[key]
  }

  if (!raw) return null
  const d = new Date(raw)
  return isNaN(d.getTime()) ? null : d
}

function addDays(date: Date, days: number): Date {
  const d = new Date(date)
  d.setDate(d.getDate() + days)
  return d
}

// ─── Service ──────────────────────────────────────────────────────────────────

@Injectable()
export class MatterDeadlinesService {
  constructor(
    @InjectModel(MatterDeadline.name)  private deadlineModel:  Model<MatterDeadlineDocument>,
    @InjectModel(ObligationRule.name)  private ruleModel:      Model<ObligationRuleDocument>,
    @InjectModel(Matter.name)          private matterModel:    Model<MatterDocument>,
    @InjectModel(ClientFolder.name)    private clientModel:    Model<ClientFolderDocument>,
  ) {}

  // ── Obligation Rules ────────────────────────────────────────────────────────

  async seedRules(): Promise<{ inserted: number; skipped: number }> {
    let inserted = 0, skipped = 0
    for (const rule of STANDARD_OBLIGATION_RULES) {
      const exists = await this.ruleModel.exists({ obligationKey: rule.obligationKey })
      if (exists) { skipped++; continue }
      await this.ruleModel.create(rule)
      inserted++
    }
    return { inserted, skipped }
  }

  async findAllRules(practiceArea?: string): Promise<ObligationRuleDocument[]> {
    const filter: any = { isActive: true }
    if (practiceArea) filter.practiceArea = practiceArea
    return this.ruleModel.find(filter).sort({ practiceArea: 1, name: 1 }).lean() as any
  }

  // ── Generate Proposals ──────────────────────────────────────────────────────
  // Reads matter + client data, finds applicable ObligationRules, generates
  // proposed MatterDeadline records. Skips obligations already in the DB.

  async generateProposals(matterId: string): Promise<{
    created: MatterDeadlineDocument[]
    skipped: number
    missingAnchors: string[]
  }> {
    const matter = await this.matterModel.findById(matterId).lean()
    if (!matter) throw new NotFoundException(`Matter ${matterId} not found`)

    const client = await this.clientModel.findById((matter as any).clientFolderId).lean()
    if (!client) throw new NotFoundException(`Client for matter ${matterId} not found`)

    // Find applicable rules — match practiceArea and optionally subType
    const rules = await this.ruleModel.find({ isActive: true, practiceArea: (matter as any).practiceArea }).lean() as any[]

    const subType: string = (matter as any).subType ?? ''
    const applicable = rules.filter(r =>
      r.matterSubTypes.length === 0 || r.matterSubTypes.includes(subType)
    )

    const created: MatterDeadlineDocument[] = []
    const missingAnchors: string[] = []

    for (const rule of applicable) {
      // Check if already exists (any status) to avoid duplicates
      const exists = await this.deadlineModel.exists({
        matterId: new Types.ObjectId(matterId),
        obligationKey: rule.obligationKey,
        recurrenceIndex: 0,
      })
      if (exists) continue

      const anchorDate = resolveAnchor(rule.anchorField, client, matter)
      if (!anchorDate) {
        missingAnchors.push(`${rule.obligationKey} (needs ${rule.anchorField})`)
        continue
      }

      const dueDate      = addDays(anchorDate, rule.offsetDays)
      const reminderDate = addDays(dueDate, -rule.reminderLeadDays)

      const deadline = await this.deadlineModel.create({
        matterId:          new Types.ObjectId(matterId),
        clientFolderId:    (matter as any).clientFolderId,
        obligationRuleId:  rule._id,
        obligationKey:     rule.obligationKey,
        name:              rule.name,
        description:       rule.description,
        dueDate,
        anchorDate,
        anchorField:       rule.anchorField,
        intervalDays:      rule.intervalDays,
        recurrenceIndex:   0,
        reminderDate,
        linkedFormIds:     rule.linkedFormIds ?? [],
        status:            'proposed',
        proposedBy:        'system',
      })
      created.push(deadline)
    }

    return { created, skipped: applicable.length - created.length - missingAnchors.length, missingAnchors }
  }

  // ── CRUD ────────────────────────────────────────────────────────────────────

  async findByMatter(matterId: string): Promise<MatterDeadlineDocument[]> {
    return this.deadlineModel
      .find({ matterId: new Types.ObjectId(matterId) })
      .sort({ dueDate: 1 })
      .lean() as any
  }

  async findByClient(clientFolderId: string): Promise<MatterDeadlineDocument[]> {
    return this.deadlineModel
      .find({ clientFolderId: new Types.ObjectId(clientFolderId) })
      .sort({ dueDate: 1 })
      .lean() as any
  }

  async findUpcoming(daysAhead = 30): Promise<MatterDeadlineDocument[]> {
    const now    = new Date()
    const cutoff = addDays(now, daysAhead)
    return this.deadlineModel
      .find({
        status:  { $in: ['confirmed', 'proposed'] },
        dueDate: { $gte: now, $lte: cutoff },
      })
      .sort({ dueDate: 1 })
      .lean() as any
  }

  // ── Confirmation actions ────────────────────────────────────────────────────

  async confirm(
    id: string,
    confirmedBy: string,
    edits?: Partial<{ dueDate: string; reminderDate: string; notes: string; linkedFormIds: string[] }>,
  ): Promise<MatterDeadlineDocument> {
    const doc = await this.deadlineModel.findById(id)
    if (!doc) throw new NotFoundException(`MatterDeadline ${id} not found`)

    const editDelta: Record<string, { from: any; to: any }> = {}

    if (edits?.dueDate && edits.dueDate !== doc.dueDate.toISOString()) {
      editDelta.dueDate = { from: doc.dueDate, to: new Date(edits.dueDate) }
      doc.dueDate = new Date(edits.dueDate)
    }
    if (edits?.reminderDate && edits.reminderDate !== doc.reminderDate.toISOString()) {
      editDelta.reminderDate = { from: doc.reminderDate, to: new Date(edits.reminderDate) }
      doc.reminderDate = new Date(edits.reminderDate)
    }
    if (edits?.notes !== undefined) {
      editDelta.notes = { from: doc.notes, to: edits.notes }
      doc.notes = edits.notes
    }
    if (edits?.linkedFormIds) {
      editDelta.linkedFormIds = { from: doc.linkedFormIds, to: edits.linkedFormIds }
      doc.linkedFormIds = edits.linkedFormIds
    }

    doc.status      = 'confirmed'
    doc.confirmedBy = confirmedBy
    doc.confirmedAt = new Date()
    if (Object.keys(editDelta).length > 0) doc.editDelta = editDelta

    await doc.save()

    // If recurring, generate next occurrence
    if (doc.intervalDays) {
      await this.generateNextRecurrence(doc)
    }

    return doc.toObject() as any
  }

  async reject(id: string, reason?: string): Promise<MatterDeadlineDocument> {
    const doc = await this.deadlineModel.findByIdAndUpdate(
      id,
      { status: 'rejected', rejectionReason: reason },
      { new: true },
    ).lean()
    if (!doc) throw new NotFoundException(`MatterDeadline ${id} not found`)
    return doc as any
  }

  async complete(id: string): Promise<MatterDeadlineDocument> {
    const doc = await this.deadlineModel.findById(id)
    if (!doc) throw new NotFoundException(`MatterDeadline ${id} not found`)
    doc.status = 'completed'
    await doc.save()

    // If recurring, generate next occurrence if not already done
    if (doc.intervalDays && doc.status === 'completed') {
      const nextIndex = doc.recurrenceIndex + 1
      const exists = await this.deadlineModel.exists({
        matterId:        doc.matterId,
        obligationKey:   doc.obligationKey,
        recurrenceIndex: nextIndex,
      })
      if (!exists) await this.generateNextRecurrence(doc)
    }

    return doc.toObject() as any
  }

  async manualCreate(data: {
    matterId: string
    name: string
    description?: string
    dueDate: string
    reminderLeadDays?: number
    linkedFormIds?: string[]
    notes?: string
  }): Promise<MatterDeadlineDocument> {
    const matter = await this.matterModel.findById(data.matterId).lean()
    if (!matter) throw new NotFoundException(`Matter ${data.matterId} not found`)

    const dueDate      = new Date(data.dueDate)
    const leadDays     = data.reminderLeadDays ?? 7
    const reminderDate = addDays(dueDate, -leadDays)

    return this.deadlineModel.create({
      matterId:       new Types.ObjectId(data.matterId),
      clientFolderId: (matter as any).clientFolderId,
      obligationKey:  `manual_${Date.now()}`,
      name:           data.name,
      description:    data.description,
      dueDate,
      reminderDate,
      linkedFormIds:  data.linkedFormIds ?? [],
      recurrenceIndex: 0,
      status:         'proposed',
      proposedBy:     'manual',
      notes:          data.notes,
    })
  }

  async remove(id: string): Promise<void> {
    const doc = await this.deadlineModel.findById(id)
    if (!doc) throw new NotFoundException(`MatterDeadline ${id} not found`)
    await doc.deleteOne()
  }

  // ── Internal ────────────────────────────────────────────────────────────────

  private async generateNextRecurrence(current: MatterDeadlineDocument): Promise<void> {
    if (!current.intervalDays) return

    const nextIndex = current.recurrenceIndex + 1
    const exists = await this.deadlineModel.exists({
      matterId:        current.matterId,
      obligationKey:   current.obligationKey,
      recurrenceIndex: nextIndex,
    })
    if (exists) return

    // Check max recurrences
    if (current.obligationRuleId) {
      const rule = await this.ruleModel.findById(current.obligationRuleId).lean() as any
      if (rule?.maxRecurrences && nextIndex >= rule.maxRecurrences) return
    }

    const nextDue      = addDays(current.dueDate, current.intervalDays)
    const leadDays     = current.obligationRuleId
      ? ((await this.ruleModel.findById(current.obligationRuleId).lean()) as any)?.reminderLeadDays ?? 14
      : 14
    const nextReminder = addDays(nextDue, -leadDays)

    await this.deadlineModel.create({
      matterId:         current.matterId,
      clientFolderId:   current.clientFolderId,
      obligationRuleId: current.obligationRuleId,
      obligationKey:    current.obligationKey,
      name:             current.name,
      description:      current.description,
      dueDate:          nextDue,
      anchorDate:       current.dueDate,   // previous occurrence is new anchor
      anchorField:      current.anchorField,
      intervalDays:     current.intervalDays,
      recurrenceIndex:  nextIndex,
      reminderDate:     nextReminder,
      linkedFormIds:    current.linkedFormIds,
      status:           'proposed',
      proposedBy:       'system',
    })
  }
}
