import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
import { InputsModule } from '@progress/kendo-angular-inputs';
import { ButtonsModule } from '@progress/kendo-angular-buttons';
import { DialogsModule } from '@progress/kendo-angular-dialog';
import { DropDownsModule } from '@progress/kendo-angular-dropdowns';
import { DateInputsModule } from '@progress/kendo-angular-dateinputs';
import { MinistryApiService } from '../../services/ministry-api.service';
import { ExpenseCategoryApiService } from '../../services/expense-category-api.service';
import { ExpenseApiService } from '../../services/expense-api.service';
import { ExpenseSnapshotApiService } from '../../services/expense-snapshot-api.service';
import { ExpenseSnapshotDto, CreateExpenseSnapshotRequest } from '../../models/expense-snapshot.model';
import { ExpenseAiService } from '../../services/expense-ai.service';
import { MemberApiService } from '../../../members/services/member-api.service';
import { MemberListItemDto, memberDisplayName } from '../../../members/models/member.model';
import { Payee1099ApiService } from '../../../payee1099/services/payee1099-api.service';
import { Payee1099ListItem } from '../../../payee1099/models/payee1099.model';
import {
  MinistryDto, ExpenseCategoryGroupDto, ExpenseSubCategoryDto, ExpenseType, CreateExpenseRequest,
  ExpenseDto, FunctionalClass, ExpenseAiSuggestion,
} from '../../models/expense.model';

export interface ExpenseFormResult {
  request: CreateExpenseRequest;
  receipt: File | null;
  /** When true (continuous-entry mode), the parent should keep the dialog open after saving. */
  continueEntry: boolean;
}

/** Flattened member item with a single displayName field for the dropdown. */
interface MemberOption { id: number; displayName: string; }

/** One editable category line. `subs` holds the sub-category list for this row's chosen group. */
interface ExpenseLineForm {
  categoryGroupId: number | null;
  subCategoryId: number | null;
  amount: number;
  description: string;
  /** Functional class is no longer exposed in the form (too complex for volunteers); it stays
   *  null = inherit the ministry default. Kept here so existing overrides survive an edit. */
  functionalClass: FunctionalClass | null;
  subs: ExpenseSubCategoryDto[];
  /** Per-line AI assist state (suggest & confirm), independent of the header assist. */
  aiLoading?: boolean;
  aiSuggestion?: ExpenseAiSuggestion | null;
}

@Component({
  selector: 'app-expense-form-dialog',
  standalone: true,
  imports: [CommonModule, FormsModule, InputsModule, ButtonsModule, DialogsModule, DropDownsModule, DateInputsModule],
  templateUrl: './expense-form-dialog.component.html',
})
export class ExpenseFormDialogComponent implements OnInit, OnDestroy {
  @Input() mode: 'vendor' | 'reimbursement' = 'reimbursement';
  @Input() allowMemberPick = false;
  @Input() title = 'New Expense';
  /** When set, the dialog prefills from this expense (with its lines) for editing. */
  @Input() expense: ExpenseDto | null = null;
  @Output() save = new EventEmitter<ExpenseFormResult>();
  @Output() cancel = new EventEmitter<void>();

  /** Native receipt file input, cleared between continuous-entry saves. */
  @ViewChild('receiptInput') receiptInput?: ElementRef<HTMLInputElement>;

  ministries: MinistryDto[] = [];
  groups: ExpenseCategoryGroupDto[] = [];

  payees: Payee1099ListItem[] = [];

  /** Saved snapshots (vendor mode only) for the "Load from snapshot" picker. */
  snapshots: ExpenseSnapshotDto[] = [];
  /** Picker binding; reset to null after each apply so the same snapshot can be re-picked. */
  selectedSnapshotId: number | null = null;

  /** "Save as snapshot" name-prompt state. */
  showSnapshotNamePrompt = false;
  snapshotName = '';
  snapshotSaving = false;

  /** Snapshot tools (load/save) are a vendor-payment feature only. */
  get showSnapshotTools(): boolean { return this.mode === 'vendor'; }

  memberResults: MemberOption[] = [];

  /** Continuous-entry toggle: keep member/ministry/category/date and the dialog open after each save. */
  continueEntry = false;

  /** The on-behalf reimbursement create flow is the only place continuous entry applies. */
  get showContinueEntry(): boolean {
    return this.mode === 'reimbursement' && this.allowMemberPick && !this.expense;
  }

  form = {
    ministryId: null as number | null,
    description: '',
    vendorName: '',
    checkNumber: '',
    memberId: null as number | null,
    payeeId: null as number | null,
    expenseDate: new Date(),
  };
  /** At least one line always; "+ Add line" appends, each line is independently removable down to one. */
  lines: ExpenseLineForm[] = [this.emptyLine()];
  receipt: File | null = null;

  // ── Receipt preview (right panel) ────────────────────────────────────────
  /** Blob URL for an image receipt, bound directly to <img [src]>. */
  receiptImageUrl: string | null = null;
  /** Sanitized blob URL for a PDF receipt, bound to <iframe [src]>. */
  receiptPdfUrl: SafeResourceUrl | null = null;
  /** Raw object URL kept so it can be revoked. */
  private receiptObjectUrl: string | null = null;

  /** Image zoom factor (1 = fit panel width); lets volunteers blow up a receipt to compare. */
  receiptZoom = 1;
  readonly minZoom = 0.5;
  readonly maxZoom = 5;

  get showReceiptPanel(): boolean { return !!(this.receiptImageUrl || this.receiptPdfUrl); }

  zoomIn(): void { this.receiptZoom = Math.min(this.maxZoom, +(this.receiptZoom + 0.25).toFixed(2)); }
  zoomOut(): void { this.receiptZoom = Math.max(this.minZoom, +(this.receiptZoom - 0.25).toFixed(2)); }
  resetZoom(): void { this.receiptZoom = 1; }

  // ── AI assist (translate description + suggest category) ────────────────────
  /** True while an assist request is in flight (disables the button, shows a spinner label). */
  aiLoading = false;
  /** The latest suggestion awaiting the user's Apply/Dismiss decision; null when none is shown. */
  aiSuggestion: ExpenseAiSuggestion | null = null;

  constructor(
    private ministryApi: MinistryApiService,
    private catApi: ExpenseCategoryApiService,
    private memberApi: MemberApiService,
    private expenseApi: ExpenseApiService,
    private snapshotApi: ExpenseSnapshotApiService,
    private aiApi: ExpenseAiService,
    private payeeApi: Payee1099ApiService,
    private sanitizer: DomSanitizer,
  ) {}

  ngOnInit(): void {
    this.ministryApi.getAll().subscribe(m => (this.ministries = m));
    this.payeeApi.getAll(false).subscribe(list => (this.payees = list));
    if (this.showSnapshotTools) this.loadSnapshots();
    this.catApi.getAll(false).subscribe(groups => {
      this.groups = groups;
      // Populate each line's sub-category list once the catalog is loaded (edit mode).
      if (this.expense) this.hydrateLineSubs();
    });
    if (this.expense) {
      this.prefill(this.expense);
      // Edit mode: load the existing receipt into the preview panel.
      if (this.expense.hasReceipt) {
        this.expenseApi.downloadReceipt(this.expense.id)
          .subscribe(blob => this.setPreview(blob, blob.type));
      }
    }
  }

  ngOnDestroy(): void { this.clearPreview(); }

  private emptyLine(): ExpenseLineForm {
    return { categoryGroupId: null, subCategoryId: null, amount: 0, description: '', functionalClass: null, subs: [] };
  }

  private prefill(expense: ExpenseDto): void {
    // expenseDate is a "yyyy-MM-dd" string; build a local Date to avoid a timezone day-shift.
    const [year, month, day] = expense.expenseDate.split('-').map(Number);
    this.form = {
      ministryId: expense.ministryId,
      description: expense.description,
      vendorName: expense.vendorName ?? '',
      checkNumber: expense.checkNumber ?? '',
      memberId: expense.memberId,
      payeeId: expense.payeeId ?? null,
      expenseDate: new Date(year, month - 1, day),
    };
    this.lines = (expense.lines ?? []).map(l => ({
      categoryGroupId: l.categoryGroupId,
      subCategoryId: l.subCategoryId,
      amount: l.amount,
      description: l.description ?? '',
      functionalClass: l.functionalClass,
      subs: [],
    }));
    if (this.lines.length === 0) this.lines = [this.emptyLine()];
  }

  /** Fill each line's sub-category list from its chosen group (used after the catalog loads on edit). */
  private hydrateLineSubs(): void {
    for (const line of this.lines) {
      line.subs = this.groups.find(g => g.id === line.categoryGroupId)?.subCategories ?? [];
    }
  }

  onLineGroupChange(line: ExpenseLineForm, groupId: number | null): void {
    line.subCategoryId = null;
    line.subs = this.groups.find(g => g.id === groupId)?.subCategories ?? [];
  }

  /** Ask the backend AI to translate the description and suggest a category; show it for confirmation. */
  requestAiAssist(): void {
    const text = this.form.description.trim();
    if (!text || this.aiLoading) return;
    this.aiLoading = true;
    this.aiSuggestion = null;
    this.aiApi.assist(text, this.totalAmount).subscribe({
      next: suggestion => { this.aiSuggestion = suggestion; this.aiLoading = false; },
      error: () => { this.aiLoading = false; },
    });
  }

  /** True once a suggestion offers at least a translation or a category to apply. */
  get hasAiSuggestion(): boolean {
    const s = this.aiSuggestion;
    return !!s && (!!this.aiSuggestedDescription || s.groupId != null);
  }

  /** Combine a suggestion's two halves into the "English / 中文" string that Apply writes. */
  private combineDescription(suggestion: ExpenseAiSuggestion | null | undefined): string {
    if (!suggestion) return '';
    const en = suggestion.englishDescription?.trim() ?? '';
    const zh = suggestion.chineseDescription?.trim() ?? '';
    if (en && zh) return `${en} / ${zh}`;
    return en || zh;
  }

  /** The description that the header Apply will write. */
  get aiSuggestedDescription(): string {
    return this.combineDescription(this.aiSuggestion);
  }

  /**
   * Apply the suggestion: set the description to "English / 中文" and set the first line's
   * category/sub. Most expenses are single-line; multi-line users adjust the rest by hand.
   */
  applyAiSuggestion(): void {
    const suggestion = this.aiSuggestion;
    if (!suggestion) return;
    if (this.aiSuggestedDescription) this.form.description = this.aiSuggestedDescription;
    if (suggestion.groupId != null) {
      const firstLine = this.lines[0];
      firstLine.categoryGroupId = suggestion.groupId;
      // Populate the sub-category list for the chosen group, then select the suggested sub.
      this.onLineGroupChange(firstLine, suggestion.groupId);
      if (suggestion.subCategoryId != null) firstLine.subCategoryId = suggestion.subCategoryId;
    }
    this.aiSuggestion = null;
  }

  dismissAiSuggestion(): void { this.aiSuggestion = null; }

  // ── Per-line AI assist ──────────────────────────────────────────────────────
  /** Ask the AI to translate this line's own note and suggest its category, using the line's amount. */
  requestLineAiAssist(line: ExpenseLineForm): void {
    const text = line.description.trim();
    if (!text || line.aiLoading) return;
    line.aiLoading = true;
    line.aiSuggestion = null;
    this.aiApi.assist(text, line.amount).subscribe({
      next: suggestion => { line.aiSuggestion = suggestion; line.aiLoading = false; },
      error: () => { line.aiLoading = false; },
    });
  }

  /** The description that this line's Apply will write: "English / 中文". */
  lineSuggestedDescription(line: ExpenseLineForm): string {
    return this.combineDescription(line.aiSuggestion);
  }

  /** True once a line suggestion offers a translation or a category to apply. */
  hasLineSuggestion(line: ExpenseLineForm): boolean {
    return !!line.aiSuggestion && (!!this.lineSuggestedDescription(line) || line.aiSuggestion.groupId != null);
  }

  /** Apply this line's suggestion to itself: set its description (bilingual) and category/sub. */
  applyLineAiSuggestion(line: ExpenseLineForm): void {
    const suggestion = line.aiSuggestion;
    if (!suggestion) return;
    const description = this.lineSuggestedDescription(line);
    if (description) line.description = description;
    if (suggestion.groupId != null) {
      line.categoryGroupId = suggestion.groupId;
      // Populate the sub-category list for the chosen group, then select the suggested sub.
      this.onLineGroupChange(line, suggestion.groupId);
      if (suggestion.subCategoryId != null) line.subCategoryId = suggestion.subCategoryId;
    }
    line.aiSuggestion = null;
  }

  dismissLineAiSuggestion(line: ExpenseLineForm): void { line.aiSuggestion = null; }

  addLine(): void { this.lines.push(this.emptyLine()); }

  removeLine(index: number): void {
    if (this.lines.length > 1) this.lines.splice(index, 1);
  }

  get totalAmount(): number {
    return this.lines.reduce((sum, l) => sum + (l.amount || 0), 0);
  }

  onMemberFilter(term: string): void {
    if (!term || term.length < 1) { this.memberResults = []; return; }
    this.memberApi.getPaged({ search: term, pageSize: 10 })
      .subscribe(r => {
        this.memberResults = r.items.map((m: MemberListItemDto) => ({
          id: m.id,
          displayName: memberDisplayName(m),
        }));
      });
  }

  onFileSelected(event: Event): void {
    const input = event.target as HTMLInputElement;
    const file = input.files?.[0] ?? null;
    this.receipt = file;
    if (file) this.setPreview(file, file.type);
  }

  /** Show a newly-selected file or a fetched existing receipt in the right-hand preview panel. */
  private setPreview(blob: Blob, contentType: string): void {
    this.clearPreview();
    this.receiptZoom = 1;
    this.receiptObjectUrl = URL.createObjectURL(blob);
    if (contentType.startsWith('image/')) {
      this.receiptImageUrl = this.receiptObjectUrl;
    } else {
      this.receiptPdfUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.receiptObjectUrl);
    }
  }

  private clearPreview(): void {
    if (this.receiptObjectUrl) { URL.revokeObjectURL(this.receiptObjectUrl); this.receiptObjectUrl = null; }
    this.receiptImageUrl = null;
    this.receiptPdfUrl = null;
  }

  private loadSnapshots(): void {
    this.snapshotApi.getAll().subscribe(list => (this.snapshots = list));
  }

  /** Apply a saved snapshot: prefill header + lines, but keep today's Expense Date. */
  applySnapshot(snapshotId: number | null): void {
    if (snapshotId == null) return;
    this.snapshotApi.getById(snapshotId).subscribe(s => {
      this.form.ministryId = s.ministryId;
      this.form.description = s.description;
      this.form.vendorName = s.vendorName ?? '';
      this.form.checkNumber = s.checkNumber ?? '';
      // Expense Date is intentionally NOT taken from the snapshot — leave it as-is (today).
      this.lines = s.lines.map(line => ({
        categoryGroupId: line.categoryGroupId,
        subCategoryId: line.subCategoryId,
        amount: line.amount,
        description: line.description ?? '',
        functionalClass: line.functionalClass,
        subs: this.groups.find(g => g.id === line.categoryGroupId)?.subCategories ?? [],
      }));
      if (this.lines.length === 0) this.lines = [this.emptyLine()];
      this.selectedSnapshotId = null;
    });
  }

  /** Open the name prompt for saving the current form as a snapshot (requires a valid form). */
  openSnapshotPrompt(): void {
    if (!this.isValid) return;
    this.snapshotName = '';
    this.showSnapshotNamePrompt = true;
  }

  cancelSnapshotPrompt(): void { this.showSnapshotNamePrompt = false; }

  /** Save the current header + lines as a named snapshot (Expense Date is not stored). */
  saveSnapshot(): void {
    const name = this.snapshotName.trim();
    if (!name || this.snapshotSaving) return;
    const request: CreateExpenseSnapshotRequest = {
      name,
      ministryId: this.form.ministryId!,
      lines: this.lines.map(l => ({
        categoryGroupId: l.categoryGroupId!,
        subCategoryId: l.subCategoryId!,
        amount: l.amount,
        functionalClass: l.functionalClass,
        description: l.description.trim() || null,
      })),
      description: this.form.description.trim(),
      vendorName: this.form.vendorName || null,
      checkNumber: this.form.checkNumber || null,
      notes: null,
    };
    this.snapshotSaving = true;
    this.snapshotApi.create(request).subscribe({
      next: () => {
        this.snapshotSaving = false;
        this.showSnapshotNamePrompt = false;
        this.loadSnapshots();
      },
      error: () => { this.snapshotSaving = false; },
    });
  }

  get isValid(): boolean {
    return !!this.form.ministryId
      && this.form.description.trim().length > 0
      && this.lines.length > 0
      && this.lines.every(l => !!l.categoryGroupId && !!l.subCategoryId && l.amount > 0);
  }

  emitSave(): void {
    if (!this.isValid) return;
    const d = this.form.expenseDate;
    const expenseDate = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
    const request: CreateExpenseRequest = {
      type: (this.mode === 'vendor' ? 'VendorPayment' : 'StaffReimbursement') as ExpenseType,
      ministryId: this.form.ministryId!,
      lines: this.lines.map(l => ({
        categoryGroupId: l.categoryGroupId!,
        subCategoryId: l.subCategoryId!,
        amount: l.amount,
        functionalClass: l.functionalClass,
        description: l.description.trim() || null,
      })),
      description: this.form.description.trim(),
      vendorName: this.mode === 'vendor' ? (this.form.vendorName || null) : null,
      memberId: this.allowMemberPick ? this.form.memberId : null,
      checkNumber: this.mode === 'vendor' ? (this.form.checkNumber || null) : null,
      expenseDate,
      notes: null,
      payeeId: this.form.payeeId,
    };
    // The request and receipt are snapshotted here, so resetting the form right
    // after emitting is safe even though the parent saves asynchronously.
    this.save.emit({ request, receipt: this.receipt, continueEntry: this.continueEntry });
    if (this.continueEntry) this.resetForNext();
  }

  /**
   * Clear only the per-entry fields, keeping Member, Ministry and Expense Date so the
   * user can immediately log the next reimbursement. Lines reset to a single blank row.
   */
  private resetForNext(): void {
    this.lines = [this.emptyLine()];
    this.form.description = '';
    this.receipt = null;
    this.clearPreview();
    if (this.receiptInput) this.receiptInput.nativeElement.value = '';
  }
}
