import { toFrappeFilterArray } from '../utils/listFilterUtils';

// ─── Interfaces ───────────────────────────────────────────────────────────────

export interface SalesInvoiceItem {
  name?: string;
  item_code?: string;
  item_name?: string;
  description?: string;
  qty?: number;
  rate?: number;
  amount?: number;
  uom?: string;
  idx?: number;
  [key: string]: any;
}

export interface SalesInvoiceTimesheet {
  name?: string;
  time_sheet?: string;
  billing_hours?: number;
  billing_amount?: number;
  activity_type?: string;
  idx?: number;
  [key: string]: any;
}

export interface SalesInvoice {
  name: string;
  naming_series?: string;
  customer?: string;
  customer_name?: string;
  posting_date?: string;
  posting_time?: string;
  currency?: string;
  status?: string;
  docstatus?: number;
  grand_total?: number;
  net_total?: number;
  total?: number;
  total_taxes_and_charges?: number;
  rounded_total?: number;
  outstanding_amount?: number;
  total_billing_hours?: number;
  total_billing_amount?: number;
  company?: string;
  items?: SalesInvoiceItem[];
  timesheets?: SalesInvoiceTimesheet[];
  owner?: string;
  creation?: string;
  modified?: string;
  [key: string]: any;
}

export interface SalesInvoiceListParams {
  filters?: Record<string, any>;
  fields?: string[];
  limit_start?: number;
  limit_page_length?: number;
  order_by?: string;
}

// ─── Service ─────────────────────────────────────────────────────────────────
class SalesInvoiceService {
  private baseURL = '';

  private async getCSRFToken(): Promise<string | null> {
    if (typeof window === 'undefined') return null;
    if ((window as any).csrf_token) return (window as any).csrf_token;
    if ((window as any).frappe?.csrf_token) return (window as any).frappe.csrf_token;
    try {
      const res = await fetch('/api/method/frappe.sessions.get_csrf_token', { credentials: 'include' });
      if (res.ok) { const json = await res.json(); if (json.message) { (window as any).csrf_token = json.message; return json.message; } }
    } catch { /* ignore */ }
    return null;
  }

  private async getHeaders(): Promise<Record<string, string>> {
    const h: Record<string, string> = { 'Content-Type': 'application/json', Accept: 'application/json' };
    const csrf = await this.getCSRFToken();
    if (csrf) h['X-Frappe-CSRF-Token'] = csrf;
    return h;
  }

  private sanitizeErrorMessage(msg: string): string {
    if (!msg) return 'Something went wrong.';
    // Strip HTML if any
    const noHtml = msg.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
    // Remove python tracebacks / long stacks
    const tbIdx = noHtml.toLowerCase().indexOf('traceback');
    const cleaned = tbIdx >= 0 ? noHtml.slice(0, tbIdx).trim() : noHtml;
    // Trim very long errors
    if (cleaned.length > 220) return `${cleaned.slice(0, 220)}…`;
    return cleaned || 'Something went wrong.';
  }

  private parseFrappeError(body: any): string {
    if (body?.exc_type === 'ValidationError') return body.exc || body.message || 'Validation error';
    if (body?.message) return this.sanitizeErrorMessage(String(body.message));
    if (body?._server_messages) {
      try {
        const msgs = JSON.parse(body._server_messages);
        const first = msgs?.[0];
        const parsed = typeof first === 'string' ? JSON.parse(first) : first;
        const m = parsed?.message || first || body._server_messages;
        return this.sanitizeErrorMessage(String(m));
      } catch {
        return this.sanitizeErrorMessage(String(body._server_messages));
      }
    }
    return 'Unknown error';
  }

  private async fetchJson(url: string, opts: RequestInit = {}): Promise<any> {
    const headers = await this.getHeaders();
    const r = await fetch(url, { credentials: 'include', headers, ...opts });
    const body = await r.json().catch(() => ({}));
    if (!r.ok) throw new Error(this.parseFrappeError(body));
    if (body.exc) throw new Error(this.parseFrappeError(body));
    return body;
  }

  async getSalesInvoices(params: SalesInvoiceListParams = {}): Promise<{ data: SalesInvoice[] }> {
    const {
      filters = {},
      fields = ['name','status','customer','customer_name','posting_date','currency','grand_total','outstanding_amount','docstatus','creation'],
      limit_start = 0,
      limit_page_length = 20,
      order_by = 'creation desc',
    } = params;
    const q = new URLSearchParams();
    q.set('fields', JSON.stringify(fields));
    q.set('limit_start', String(limit_start));
    q.set('limit_page_length', String(limit_page_length));
    q.set('order_by', order_by);
    if (Object.keys(filters).length > 0) {
      const fa = toFrappeFilterArray(filters);
      if (fa.length > 0) q.set('filters', JSON.stringify(fa));
    }
    const r = await this.fetchJson(`${this.baseURL}/api/resource/Sales Invoice?${q}`);
    return { data: r.data || [] };
  }

  /**
   * Fetch Sales Invoices linked to the given Sales Orders via Sales Invoice Item.sales_order.
   * Uses frappe.client.get_list to support child-table filters.
   */
  async getSalesInvoicesBySalesOrders(args: {
    salesOrders: string[];
    limit?: number;
    orderBy?: string;
    fields?: string[];
  }): Promise<SalesInvoice[]> {
    const {
      salesOrders,
      limit = 1000,
      orderBy = 'posting_date asc',
      fields = ['name', 'posting_date', 'customer', 'customer_name', 'currency', 'grand_total', 'docstatus', 'status'],
    } = args;
    if (!salesOrders.length) return [];

    const r = await this.fetchJson(`${this.baseURL}/api/method/frappe.client.get_list`, {
      method: 'POST',
      body: JSON.stringify({
        doctype: 'Sales Invoice',
        fields,
        filters: [['Sales Invoice Item', 'sales_order', 'in', salesOrders]],
        order_by: orderBy,
        limit_page_length: limit,
      }),
    });
    return (r.message || []) as SalesInvoice[];
  }

  /**
   * Invoices tied to a Project via document or line-level `project` (not only via Sales Order).
   * Merges header `project` and `Sales Invoice Item.project` matches — deduped by invoice name.
   */
  async getSalesInvoicesLinkedToProject(args: {
    project: string;
    limit?: number;
    orderBy?: string;
    fields?: string[];
  }): Promise<SalesInvoice[]> {
    const {
      project,
      limit = 1000,
      orderBy = 'posting_date asc',
      fields = ['name', 'posting_date', 'customer', 'customer_name', 'currency', 'grand_total', 'docstatus', 'status'],
    } = args;
    if (!project?.trim()) return [];

    const listPayload = (filters: any[]) =>
      this.fetchJson(`${this.baseURL}/api/method/frappe.client.get_list`, {
        method: 'POST',
        body: JSON.stringify({
          doctype: 'Sales Invoice',
          fields,
          filters,
          order_by: orderBy,
          limit_page_length: limit,
        }),
      }).then((r: any) => (r.message || []) as SalesInvoice[]).catch(() => [] as SalesInvoice[]);

    const [fromHeader, fromItems] = await Promise.all([
      listPayload([['project', '=', project]]),
      listPayload([['Sales Invoice Item', 'project', '=', project]]),
    ]);

    const byName = new Map<string, SalesInvoice>();
    for (const inv of fromHeader) if (inv?.name) byName.set(inv.name, inv);
    for (const inv of fromItems) if (inv?.name) byName.set(inv.name, inv);
    return [...byName.values()].sort((a, b) =>
      String(a.posting_date || '').localeCompare(String(b.posting_date || '')),
    );
  }

  async getSalesInvoiceCount(filters: Record<string, any> = {}): Promise<number> {
    const q = new URLSearchParams();
    q.set('fields', JSON.stringify(['count(name) as count']));
    if (Object.keys(filters).length > 0) {
      const fa = toFrappeFilterArray(filters);
      if (fa.length > 0) q.set('filters', JSON.stringify(fa));
    }
    try {
      const r = await this.fetchJson(`${this.baseURL}/api/resource/Sales Invoice?${q}`);
      return r.data?.[0]?.count || 0;
    } catch { return 0; }
  }

  async getSalesInvoice(name: string): Promise<SalesInvoice> {
    const r = await this.fetchJson(`${this.baseURL}/api/resource/Sales%20Invoice/${encodeURIComponent(name)}`);
    return r.data;
  }

  async createSalesInvoice(data: Partial<SalesInvoice>): Promise<SalesInvoice> {
    const r = await this.fetchJson(`${this.baseURL}/api/resource/Sales%20Invoice`, {
      method: 'POST',
      body: JSON.stringify(data),
    });
    return r.data;
  }

  async updateSalesInvoice(name: string, data: Partial<SalesInvoice>): Promise<SalesInvoice> {
    const r = await this.fetchJson(`${this.baseURL}/api/resource/Sales%20Invoice/${encodeURIComponent(name)}`, {
      method: 'PUT',
      body: JSON.stringify(data),
    });
    return r.data;
  }

  /** Directly clear stale "None" link values on SI item rows, bypassing document validation. */
  async clearItemLinkNones(invoiceName: string): Promise<void> {
    const headers = await this.getHeaders();
    // Fetch the raw SI to get child row names
    const doc = await this.fetchJson(`${this.baseURL}/api/resource/Sales%20Invoice/${encodeURIComponent(invoiceName)}`);
    const items: any[] = doc?.data?.items || [];
    const staleFields = [
      'delivery_note', 'dn_detail', 'sales_order', 'so_detail',
      'against_delivery_note', 'against_sales_order',
    ];
    for (const item of items) {
      if (!item.name) continue;
      const hasNone = staleFields.some(f => item[f] === 'None' || item[f] === 'none');
      if (!hasNone) continue;
      // frappe.client.set_value writes directly to DB without running validate()
      await this.fetchJson(`${this.baseURL}/api/method/frappe.client.set_value`, {
        method: 'POST',
        headers,
        body: JSON.stringify({
          doctype: 'Sales Invoice Item',
          name: item.name,
          fieldname: {
            delivery_note: '', dn_detail: '', sales_order: '', so_detail: '',
            against_delivery_note: '', against_sales_order: '',
          },
        }),
      });
    }
  }

  async submitSalesInvoice(name: string): Promise<SalesInvoice> {
    const r = await this.fetchJson(`${this.baseURL}/api/resource/Sales%20Invoice/${encodeURIComponent(name)}`, {
      method: 'PUT',
      body: JSON.stringify({ docstatus: 1 }),
    });
    return r.data;
  }
}

const salesInvoiceService = new SalesInvoiceService();
export default salesInvoiceService;
