import {
  Component,
  ElementRef,
  ViewChild,
  Inject,
  PLATFORM_ID,
  AfterViewInit,
  ChangeDetectorRef,
  OnDestroy,
  HostListener
} from '@angular/core';
import { isPlatformBrowser, CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { CustomSelectComponent } from '../../components/custom-select/custom-select';

import {
  BillingService,
  BillingItem,
  BillingSortBy,
  SortDir,
  TipoCliente,
  TipoFiltro,
  BillingUpdateRequest
} from '../../services/billing';
import { AuthService } from '../../services/auth.service';
import { LinesService } from '../../services/lines.service';
import { confirmDeletionWithTyping } from '../../utils/destructive-confirmation';

interface BillingClientGroup {
  cliente: string;
  total: number;        // registros
  linhas: number;       // soma qtdLinhas
  totalVivo: number;
  totalLine: number;
  lucro: number;
}

@Component({
  standalone: true,
  imports: [CommonModule, FormsModule, HttpClientModule, CustomSelectComponent],
  templateUrl: './faturamento.html',
  styleUrls: ['./faturamento.scss']
})
export class Faturamento implements AfterViewInit, OnDestroy {
  toastMessage = '';
  @ViewChild('successToast', { static: false }) successToast!: ElementRef;

  @ViewChild('detailModal', { static: false }) detailModal!: ElementRef<HTMLElement>;
  @ViewChild('compareModal', { static: false }) compareModal!: ElementRef<HTMLElement>;

  constructor(
    @Inject(PLATFORM_ID) private platformId: object,
    private billing: BillingService,
    private linesService: LinesService,
    private cdr: ChangeDetectorRef,
    private authService: AuthService
  ) {}

  loading = false;

  // filtros
  searchTerm = '';
  filterTipo: TipoFiltro = 'ALL';

  clientsList: string[] = [];
  selectedClients: string[] = [];
  showClientMenu = false;
  clientSearchTerm = '';

  // sort/paging
  sortBy: BillingSortBy = 'cliente';
  sortDir: SortDir = 'asc';

  // pagina por CLIENTES (grupos)
  page = 1;
  pageSize = 10;
  pageSizeOptions = [10, 20, 50, 100];
  total = 0; // total de grupos

  // agrupamento
  clientGroups: BillingClientGroup[] = [];
  pagedClientGroups: BillingClientGroup[] = [];
  expandedGroup: string | null = null;
  groupRows: BillingItem[] = [];
  private rowsByClient = new Map<string, BillingItem[]>();

  // KPIs
  loadingKpis = false;
  kpiTotalClientes = 0;
  kpiTotalLinhas = 0;
  kpiTotalVivo = 0;
  kpiTotalLine = 0;
  kpiLucro = 0;

  // modals
  detailOpen = false;
  compareOpen = false;
  detailData: BillingItem | null = null;
  compareData: BillingItem | null = null;
  editOpen = false;
  editSaving = false;
  editModel: BillingItem | null = null;
  editingId: string | null = null;
  deleteOpen = false;
  deleteTarget: BillingItem | null = null;

  isSysAdmin = false;

  private searchTimer: any = null;
  private searchResolvedClients: string[] = [];
  private searchResolveVersion = 0;

  // cache do ALL
  private allCache: BillingItem[] = [];
  private allCacheAt = 0;
  private allCacheTtlMs = 15000;

  // --------------------------
  // Eventos globais
  // --------------------------
  @HostListener('document:click', ['$event'])
  onDocumentClick(ev: MouseEvent) {
    if (!isPlatformBrowser(this.platformId)) return;
    if (this.anyModalOpen()) return;

    if (!this.showClientMenu) return;
    const target = ev.target as HTMLElement | null;
    if (!target) return;

    const inside = !!target.closest('.client-filter-wrap');
    if (!inside) {
      this.showClientMenu = false;
      this.cdr.detectChanges();
    }
  }

  @HostListener('document:keydown', ['$event'])
  onDocumentKeydown(ev: Event) {
    if (!isPlatformBrowser(this.platformId)) return;

    const keyboard = ev as KeyboardEvent;
    if (keyboard.key === 'Escape') {
      if (this.anyModalOpen()) {
        keyboard.preventDefault();
        keyboard.stopPropagation();
        this.closeAllModals();
        return;
      }

      if (this.showClientMenu) {
        this.showClientMenu = false;
        keyboard.stopPropagation();
        this.cdr.detectChanges();
      }
    }
  }

  ngOnDestroy(): void {
    if (this.searchTimer) clearTimeout(this.searchTimer);
  }

  async ngAfterViewInit() {
    if (!isPlatformBrowser(this.platformId)) return;

    this.initAnimations();
    this.isSysAdmin = this.authService.hasRole('sysadmin');

    setTimeout(() => {
      this.refreshData(true);
    });
  }

  private initAnimations() {
    document.documentElement.classList.add('js-animate');
    setTimeout(() => {
      const items = document.querySelectorAll<HTMLElement>('[data-animate]');
      items.forEach((el) => el.classList.add('is-visible'));
    }, 100);
  }

  // --------------------------
  // Helpers
  // --------------------------
  private anyModalOpen(): boolean {
    return !!(this.detailOpen || this.compareOpen || this.editOpen || this.deleteOpen);
  }

  closeAllModals() {
    this.detailOpen = false;
    this.compareOpen = false;
    this.detailData = null;
    this.compareData = null;
    this.editOpen = false;
    this.editModel = null;
    this.editingId = null;
    this.deleteOpen = false;
    this.deleteTarget = null;
    this.cdr.detectChanges();
  }

  /** ✅ Evita usar Number(...) no template */
  hasLucro(item: BillingItem | null): boolean {
    const n = Number((item as any)?.lucro ?? 0);
    return !Number.isNaN(n) && n !== 0;
  }

  /** ✅ Lê observação com/sem acento sem quebrar template */
  getObservacao(item: BillingItem | null): string {
    const anyItem: any = item as any;
    const v =
      anyItem?.observacao ??
      anyItem?.['observação'] ??
      anyItem?.OBSERVACAO ??
      anyItem?.['OBSERVAÇÃO'];

    const s = (v ?? '').toString().trim();
    return s ? s : '—';
  }

  private normalizeText(s: any): string {
    return (s ?? '')
      .toString()
      .trim()
      .toUpperCase()
      .normalize('NFD')
      .replace(/[\u0300-\u036f]/g, '');
  }

  private buildGlobalSearchBlob(row: BillingItem): string {
    const parts = [
      row.tipo,
      row.item,
      row.cliente,
      row.qtdLinhas,
      row.franquiaVivo,
      row.valorContratoVivo,
      row.franquiaLine,
      row.valorContratoLine,
      row.lucro,
      row.aparelho,
      row.formaPagamento,
    ];

    return this.normalizeText(parts.join(' '));
  }

  private matchesTipo(itemTipo: any, filtro: TipoFiltro): boolean {
    if (filtro === 'ALL') return true;

    const t = this.normalizeText(itemTipo);

    if (filtro === 'PF') return t === 'PF' || t.includes('FISICA');
    if (filtro === 'PJ') return t === 'PJ' || t.includes('JURIDICA');

    return true;
  }

  formatMoney(v: any): string {
    const n = Number(v);
    if (v === null || v === undefined || Number.isNaN(n)) return '—';
    return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(n);
  }

  formatFranquia(v: any): string {
    if (v === null || v === undefined) return '—';
    if (typeof v === 'string') {
      const s = v.trim();
      if (!s) return '—';
      if (/[A-Z]/i.test(s)) return s;
      const n = Number(s.replace(',', '.'));
      if (Number.isNaN(n)) return s;
      return `${n.toLocaleString('pt-BR')} GB`;
    }

    const n = Number(v);
    if (Number.isNaN(n)) return '—';
    return `${n.toLocaleString('pt-BR')} GB`;
  }

  // --------------------------
  // Filtros / Clientes
  // --------------------------
  toggleClientMenu() {
    this.showClientMenu = !this.showClientMenu;
  }

  closeClientDropdown() {
    this.showClientMenu = false;
  }

  isClientSelected(client: string): boolean {
    return this.selectedClients.includes(client);
  }

  get filteredClientsList(): string[] {
    const base = this.clientsList ?? [];
    if (!this.clientSearchTerm) return base;
    const s = this.clientSearchTerm.toLowerCase();
    return base.filter((c) => (c ?? '').toLowerCase().includes(s));
  }

  selectClient(client: string | null) {
    if (client === null) {
      this.selectedClients = [];
    } else {
      const idx = this.selectedClients.indexOf(client);
      if (idx >= 0) this.selectedClients.splice(idx, 1);
      else this.selectedClients.push(client);
    }

    this.page = 1;
    this.expandedGroup = null;
    this.groupRows = [];
    this.refreshData();
  }

  removeClient(client: string, event: Event) {
    event.stopPropagation();
    const idx = this.selectedClients.indexOf(client);
    if (idx >= 0) this.selectedClients.splice(idx, 1);

    this.page = 1;
    this.expandedGroup = null;
    this.groupRows = [];
    this.refreshData();
  }

  clearClientSelection(event?: Event) {
    if (event) event.stopPropagation();
    this.selectedClients = [];
    this.clientSearchTerm = '';
    this.page = 1;
    this.expandedGroup = null;
    this.groupRows = [];
    this.refreshData();
  }

  setFilter(type: 'ALL' | TipoCliente) {
    if (this.filterTipo === type) return;

    this.filterTipo = type;
    this.selectedClients = [];
    this.clientSearchTerm = '';
    this.page = 1;
    this.expandedGroup = null;
    this.groupRows = [];

    this.refreshData(true);
  }

  // --------------------------
  // Search
  // --------------------------
  onSearch() {
    if (this.searchTimer) clearTimeout(this.searchTimer);

    this.searchTimer = setTimeout(async () => {
      this.page = 1;
      this.expandedGroup = null;
      this.groupRows = [];
      await this.resolveSearchClientsByLineOrChip();
      this.refreshData();
    }, 250);
  }

  clearSearch() {
    this.searchTerm = '';
    this.searchResolvedClients = [];
    this.page = 1;
    this.expandedGroup = null;
    this.groupRows = [];
    this.refreshData();
  }

  private isSpecificLineOrChipSearch(term: string): boolean {
    const digits = (term ?? '').replace(/\D/g, '');
    return digits.length >= 8;
  }

  private async resolveSearchClientsByLineOrChip(): Promise<void> {
    const term = (this.searchTerm ?? '').trim();
    const requestVersion = ++this.searchResolveVersion;

    if (!term || !this.isSpecificLineOrChipSearch(term)) {
      this.searchResolvedClients = [];
      return;
    }

    try {
      const response = await new Promise<any>((resolve, reject) => {
        this.linesService.getLines(1, 200, term).subscribe({
          next: resolve,
          error: reject
        });
      });

      if (requestVersion !== this.searchResolveVersion) return;

      const clients = (response?.items ?? [])
        .map((x: any) => (x?.cliente ?? '').toString().trim())
        .filter((x: string) => !!x);

      this.searchResolvedClients = Array.from(new Set(clients));
    } catch {
      if (requestVersion !== this.searchResolveVersion) return;
      this.searchResolvedClients = [];
    }
  }

  // --------------------------
  // Data
  // --------------------------
  refreshData(forceReloadAll = false) {
    this.loadAllAndApply(forceReloadAll);
  }

  private getAllItems(force = false): Promise<BillingItem[]> {
    const now = Date.now();

    if (!force && this.allCache.length > 0 && (now - this.allCacheAt) < this.allCacheTtlMs) {
      return Promise.resolve(this.allCache);
    }

    return new Promise((resolve) => {
      this.billing.getAll().subscribe({
        next: (items) => {
          this.allCache = (items ?? []);
          this.allCacheAt = Date.now();
          resolve(this.allCache);
        },
        error: () => resolve(this.allCache ?? [])
      });
    });
  }

  private rebuildClientsList(baseTipo: BillingItem[]) {
    const set = new Set<string>();
    for (const r of baseTipo) {
      const c = (r.cliente ?? '').trim();
      if (c) set.add(c);
    }
    this.clientsList = Array.from(set).sort((a, b) => a.localeCompare(b));
  }

  private buildGroups(items: BillingItem[]) {
    this.rowsByClient.clear();

    const safeClient = (c: any) => (String(c ?? '').trim() || 'SEM CLIENTE');

    for (const r of (items ?? [])) {
      const key = safeClient(r.cliente);
      (r as any).cliente = key;

      const arr = this.rowsByClient.get(key) ?? [];
      arr.push(r);
      this.rowsByClient.set(key, arr);
    }

    const groups: BillingClientGroup[] = [];
    this.rowsByClient.forEach((arr, cliente) => {
      let linhas = 0;
      let totalVivo = 0;
      let totalLine = 0;
      let lucro = 0;

      for (const x of arr) {
        linhas += Number(x.qtdLinhas ?? 0) || 0;
        totalVivo += Number(x.valorContratoVivo ?? 0) || 0;
        totalLine += Number(x.valorContratoLine ?? 0) || 0;
        lucro += Number((x as any).lucro ?? 0) || 0;
      }

      groups.push({
        cliente,
        total: arr.length,
        linhas,
        totalVivo: Number(totalVivo.toFixed(2)),
        totalLine: Number(totalLine.toFixed(2)),
        lucro: Number(lucro.toFixed(2))
      });
    });

    groups.sort((a, b) => a.cliente.localeCompare(b.cliente, 'pt-BR', { sensitivity: 'base' }));

    this.clientGroups = groups;
    this.total = groups.length;

    this.applyGroupPagination();
  }

  private applyGroupPagination() {
    const start = (this.page - 1) * this.pageSize;
    const end = start + this.pageSize;
    this.pagedClientGroups = (this.clientGroups ?? []).slice(start, end);

    if (this.expandedGroup && !this.pagedClientGroups.some(g => g.cliente === this.expandedGroup)) {
      this.expandedGroup = null;
      this.groupRows = [];
    }
  }

  private sortRows(arr: BillingItem[]): BillingItem[] {
    const dir = this.sortDir === 'asc' ? 1 : -1;

    const getVal = (r: BillingItem) => {
      switch (this.sortBy) {
        case 'tipo': return (r.tipo ?? '').toString();
        case 'item': return r.item ?? 0;
        case 'cliente': return (r.cliente ?? '').toString();
        case 'qtdlinhas': return r.qtdLinhas ?? 0;
        case 'franquiavivo': return r.franquiaVivo ?? 0;
        case 'valorcontratovivo': return r.valorContratoVivo ?? 0;
        case 'franquialine': return r.franquiaLine ?? 0;
        case 'valorcontratoline': return r.valorContratoLine ?? 0;
        case 'lucro': return (r as any).lucro ?? 0;
        case 'aparelho': return (r.aparelho ?? '').toString();
        case 'formapagamento': return (r.formaPagamento ?? '').toString();
        default: return (r.cliente ?? '').toString();
      }
    };

    return [...(arr ?? [])].sort((a, b) => {
      const va = getVal(a);
      const vb = getVal(b);
      if (typeof va === 'number' && typeof vb === 'number') return (va - vb) * dir;
      return String(va).localeCompare(String(vb), 'pt-BR', { sensitivity: 'base' }) * dir;
    });
  }

  toggleGroup(cliente: string) {
    if (this.expandedGroup === cliente) {
      this.expandedGroup = null;
      this.groupRows = [];
      return;
    }

    this.expandedGroup = cliente;
    const rows = this.rowsByClient.get(cliente) ?? [];
    this.groupRows = this.sortRows(rows);

    this.cdr.detectChanges();
  }

  private applyClientSide(allItems: BillingItem[]) {
    const baseTipo = (allItems ?? []).filter((r) => this.matchesTipo(r.tipo, this.filterTipo));
    this.rebuildClientsList(baseTipo);

    let arr = [...baseTipo];

    if (this.selectedClients.length > 0) {
      const set = new Set(this.selectedClients.map((x) => this.normalizeText(x)));
      arr = arr.filter((r) => set.has(this.normalizeText(r.cliente)));
    }

    const term = this.normalizeText(this.searchTerm);
    const resolvedClientsSet = new Set((this.searchResolvedClients ?? []).map((x) => this.normalizeText(x)));
    if (term) {
      arr = arr.filter((r) =>
        this.buildGlobalSearchBlob(r).includes(term) ||
        (resolvedClientsSet.size > 0 && resolvedClientsSet.has(this.normalizeText(r.cliente)))
      );
    }

    // KPIs
    const unique = new Set<string>();
    let totalLinhas = 0;
    let totalVivo = 0;
    let totalLine = 0;
    let totalLucro = 0;
    const clientTotals = new Map<string, { vivo: number; line: number; lucro: number }>();

    for (const r of arr) {
      const c = (r.cliente ?? '').trim();
      if (c) unique.add(c);

      totalLinhas += Number(r.qtdLinhas ?? 0) || 0;

      const key = this.normalizeText(c);
      if (!key) continue;

      const vivo = Number(r.valorContratoVivo ?? 0) || 0;
      const line = Number(r.valorContratoLine ?? 0) || 0;
      const lucro = Number((r as any).lucro ?? 0) || 0;

      const existing = clientTotals.get(key);
      if (!existing) {
        clientTotals.set(key, { vivo, line, lucro });
      } else {
        if (!existing.vivo && vivo) existing.vivo = vivo;
        if (!existing.line && line) existing.line = line;
        if (!existing.lucro && lucro) existing.lucro = lucro;
      }
    }

    for (const vals of clientTotals.values()) {
      totalVivo += vals.vivo;
      totalLine += vals.line;
      totalLucro += vals.lucro;
    }

    this.kpiTotalClientes = unique.size;
    this.kpiTotalLinhas = totalLinhas;
    this.kpiTotalVivo = Number(totalVivo.toFixed(2));
    this.kpiTotalLine = Number(totalLine.toFixed(2));
    this.kpiLucro = Number(totalLucro.toFixed(2));

    this.buildGroups(arr);
  }

  private async loadAllAndApply(forceReloadAll = false) {
    this.loading = true;
    this.loadingKpis = true;

    this.clientGroups = [];
    this.pagedClientGroups = [];
    this.rowsByClient.clear();
    this.groupRows = [];

    try {
      const all = await this.getAllItems(forceReloadAll);
      this.applyClientSide(all);
    } finally {
      this.loading = false;
      this.loadingKpis = false;
      this.cdr.detectChanges();
    }
  }

  // --------------------------
  // Sort / Paging
  // --------------------------
  setSort(key: BillingSortBy) {
    if (this.sortBy === key) this.sortDir = this.sortDir === 'asc' ? 'desc' : 'asc';
    else {
      this.sortBy = key;
      this.sortDir = 'asc';
    }

    if (this.expandedGroup) {
      const rows = this.rowsByClient.get(this.expandedGroup) ?? [];
      this.groupRows = this.sortRows(rows);
    }

    this.cdr.detectChanges();
  }

  onPageSizeChange() {
    this.page = 1;
    this.applyGroupPagination();
    this.cdr.detectChanges();
  }

  goToPage(p: number) {
    this.page = Math.max(1, Math.min(this.totalPages, p));
    this.applyGroupPagination();
    this.cdr.detectChanges();
  }

  trackById(_: number, row: BillingItem) {
    return row.id;
  }

  trackByCliente(_: number, g: BillingClientGroup) {
    return g.cliente;
  }

  get totalPages() {
    return Math.ceil((this.total || 0) / this.pageSize) || 1;
  }

  get pageStart() {
    return this.total === 0 ? 0 : (this.page - 1) * this.pageSize + 1;
  }

  get pageEnd() {
    return this.total === 0 ? 0 : Math.min(this.page * this.pageSize, this.total);
  }

  get pageNumbers() {
    const total = this.totalPages;
    const current = this.page;
    const max = 5;
    let start = Math.max(1, current - 2);
    let end = Math.min(total, start + (max - 1));
    start = Math.max(1, end - (max - 1));
    const pages: number[] = [];
    for (let i = start; i <= end; i++) pages.push(i);
    return pages;
  }

  // --------------------------
  // Modals
  // --------------------------
  onDetalhes(r: BillingItem) {
    this.detailOpen = true;
    this.detailData = null;
    this.billing.getById(r.id).subscribe({
      next: (data) => {
        this.detailData = data ?? r;
        this.cdr.detectChanges();
      },
      error: () => {
        this.detailData = r;
        this.cdr.detectChanges();
      }
    });
  }

  onComparativo(r: BillingItem) {
    this.compareOpen = true;
    this.compareData = r;
    this.cdr.detectChanges();
  }

  onEditar(r: BillingItem) {
    if (!this.isSysAdmin) return;
    this.editingId = r.id;
    this.editModel = { ...r };
    this.editOpen = true;
    this.cdr.detectChanges();
  }

  closeEdit() {
    this.editOpen = false;
    this.editModel = null;
    this.editingId = null;
    this.cdr.detectChanges();
  }

  onDelete(r: BillingItem) {
    if (!this.isSysAdmin) return;
    this.deleteTarget = r;
    this.deleteOpen = true;
    this.cdr.detectChanges();
  }

  cancelDelete() {
    this.deleteOpen = false;
    this.deleteTarget = null;
    this.cdr.detectChanges();
  }

  async confirmDelete() {
    if (!this.deleteTarget) return;
    if (!(await confirmDeletionWithTyping('este registro de faturamento'))) return;
    const id = this.deleteTarget.id;
    this.billing.remove(id).subscribe({
      next: () => {
        this.deleteOpen = false;
        this.deleteTarget = null;
        this.refreshData(true);
        this.cdr.detectChanges();
      },
      error: () => {
        this.deleteOpen = false;
        this.deleteTarget = null;
        this.cdr.detectChanges();
      }
    });
  }

  saveEdit() {
    if (!this.editModel || !this.editingId) return;
    this.editSaving = true;

    const payload: BillingUpdateRequest = {
      tipo: this.editModel.tipo,
      item: this.toNullableNumber(this.editModel.item) as number | null,
      cliente: (this.editModel.cliente ?? '').toString(),
      qtdLinhas: this.toNullableNumber(this.editModel.qtdLinhas),
      franquiaVivo: this.toNullableNumber(this.editModel.franquiaVivo),
      valorContratoVivo: this.toNullableNumber(this.editModel.valorContratoVivo),
      franquiaLine: this.toNullableNumber(this.editModel.franquiaLine),
      valorContratoLine: this.toNullableNumber(this.editModel.valorContratoLine),
      lucro: this.toNullableNumber(this.editModel.lucro),
      aparelho: this.editModel.aparelho ?? null,
      formaPagamento: this.editModel.formaPagamento ?? null
    };

    this.billing.update(this.editingId, payload).subscribe({
      next: () => {
        this.editSaving = false;
        this.closeEdit();
        this.refreshData(true);
      },
      error: () => {
        this.editSaving = false;
      }
    });
  }

  private toNullableNumber(value: any): number | null {
    if (value === undefined || value === null || value === '') return null;
    const n = Number(value);
    return Number.isNaN(n) ? null : n;
  }
}
