import { query, queryOne, getClient } from '../../config/database.js';
import { NotFoundError, ConflictError, ValidationError } from '../../shared/errors/index.js';
import { taxesService } from '../financial/taxes.service.js';
import { sequencesService, SEQUENCE_CODES } from '../core/sequences.service.js';

export interface SalesOrderLine {
  id: string;
  order_id: string;
  product_id: string;
  product_name?: string;
  description: string;
  quantity: number;
  qty_delivered: number;
  qty_invoiced: number;
  uom_id: string;
  uom_name?: string;
  price_unit: number;
  discount: number;
  tax_ids: string[];
  amount_untaxed: number;
  amount_tax: number;
  amount_total: number;
  analytic_account_id?: string;
}

export interface SalesOrder {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  name: string;
  client_order_ref?: string;
  partner_id: string;
  partner_name?: string;
  order_date: Date;
  validity_date?: Date;
  commitment_date?: Date;
  currency_id: string;
  currency_code?: string;
  pricelist_id?: string;
  pricelist_name?: string;
  payment_term_id?: string;
  user_id?: string;
  user_name?: string;
  sales_team_id?: string;
  sales_team_name?: string;
  amount_untaxed: number;
  amount_tax: number;
  amount_total: number;
  status: 'draft' | 'sent' | 'sale' | 'done' | 'cancelled';
  invoice_status: 'pending' | 'partial' | 'invoiced';
  delivery_status: 'pending' | 'partial' | 'delivered';
  invoice_policy: 'order' | 'delivery';
  picking_id?: string;
  notes?: string;
  terms_conditions?: string;
  lines?: SalesOrderLine[];
  created_at: Date;
  confirmed_at?: Date;
}

export interface CreateSalesOrderDto {
  company_id: string;
  partner_id: string;
  client_order_ref?: string;
  order_date?: string;
  validity_date?: string;
  commitment_date?: string;
  currency_id: string;
  pricelist_id?: string;
  payment_term_id?: string;
  sales_team_id?: string;
  invoice_policy?: 'order' | 'delivery';
  notes?: string;
  terms_conditions?: string;
}

export interface UpdateSalesOrderDto {
  partner_id?: string;
  client_order_ref?: string | null;
  order_date?: string;
  validity_date?: string | null;
  commitment_date?: string | null;
  currency_id?: string;
  pricelist_id?: string | null;
  payment_term_id?: string | null;
  sales_team_id?: string | null;
  invoice_policy?: 'order' | 'delivery';
  notes?: string | null;
  terms_conditions?: string | null;
}

export interface CreateSalesOrderLineDto {
  product_id: string;
  description: string;
  quantity: number;
  uom_id: string;
  price_unit: number;
  discount?: number;
  tax_ids?: string[];
  analytic_account_id?: string;
}

export interface UpdateSalesOrderLineDto {
  description?: string;
  quantity?: number;
  uom_id?: string;
  price_unit?: number;
  discount?: number;
  tax_ids?: string[];
  analytic_account_id?: string | null;
}

export interface SalesOrderFilters {
  company_id?: string;
  partner_id?: string;
  status?: string;
  invoice_status?: string;
  delivery_status?: string;
  date_from?: string;
  date_to?: string;
  search?: string;
  page?: number;
  limit?: number;
}

class OrdersService {
  async findAll(tenantId: string, filters: SalesOrderFilters = {}): Promise<{ data: SalesOrder[]; total: number }> {
    const { company_id, partner_id, status, invoice_status, delivery_status, date_from, date_to, search, page = 1, limit = 20 } = filters;
    const offset = (page - 1) * limit;

    let whereClause = 'WHERE so.tenant_id = $1';
    const params: any[] = [tenantId];
    let paramIndex = 2;

    if (company_id) {
      whereClause += ` AND so.company_id = $${paramIndex++}`;
      params.push(company_id);
    }

    if (partner_id) {
      whereClause += ` AND so.partner_id = $${paramIndex++}`;
      params.push(partner_id);
    }

    if (status) {
      whereClause += ` AND so.status = $${paramIndex++}`;
      params.push(status);
    }

    if (invoice_status) {
      whereClause += ` AND so.invoice_status = $${paramIndex++}`;
      params.push(invoice_status);
    }

    if (delivery_status) {
      whereClause += ` AND so.delivery_status = $${paramIndex++}`;
      params.push(delivery_status);
    }

    if (date_from) {
      whereClause += ` AND so.order_date >= $${paramIndex++}`;
      params.push(date_from);
    }

    if (date_to) {
      whereClause += ` AND so.order_date <= $${paramIndex++}`;
      params.push(date_to);
    }

    if (search) {
      whereClause += ` AND (so.name ILIKE $${paramIndex} OR so.client_order_ref ILIKE $${paramIndex} OR p.name ILIKE $${paramIndex})`;
      params.push(`%${search}%`);
      paramIndex++;
    }

    const countResult = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count
       FROM sales.sales_orders so
       LEFT JOIN core.partners p ON so.partner_id = p.id
       ${whereClause}`,
      params
    );

    params.push(limit, offset);
    const data = await query<SalesOrder>(
      `SELECT so.*,
              c.name as company_name,
              p.name as partner_name,
              cu.code as currency_code,
              pl.name as pricelist_name,
              u.name as user_name,
              st.name as sales_team_name
       FROM sales.sales_orders so
       LEFT JOIN auth.companies c ON so.company_id = c.id
       LEFT JOIN core.partners p ON so.partner_id = p.id
       LEFT JOIN core.currencies cu ON so.currency_id = cu.id
       LEFT JOIN sales.pricelists pl ON so.pricelist_id = pl.id
       LEFT JOIN auth.users u ON so.user_id = u.id
       LEFT JOIN sales.sales_teams st ON so.sales_team_id = st.id
       ${whereClause}
       ORDER BY so.created_at DESC
       LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
      params
    );

    return {
      data,
      total: parseInt(countResult?.count || '0', 10),
    };
  }

  async findById(id: string, tenantId: string): Promise<SalesOrder> {
    const order = await queryOne<SalesOrder>(
      `SELECT so.*,
              c.name as company_name,
              p.name as partner_name,
              cu.code as currency_code,
              pl.name as pricelist_name,
              u.name as user_name,
              st.name as sales_team_name
       FROM sales.sales_orders so
       LEFT JOIN auth.companies c ON so.company_id = c.id
       LEFT JOIN core.partners p ON so.partner_id = p.id
       LEFT JOIN core.currencies cu ON so.currency_id = cu.id
       LEFT JOIN sales.pricelists pl ON so.pricelist_id = pl.id
       LEFT JOIN auth.users u ON so.user_id = u.id
       LEFT JOIN sales.sales_teams st ON so.sales_team_id = st.id
       WHERE so.id = $1 AND so.tenant_id = $2`,
      [id, tenantId]
    );

    if (!order) {
      throw new NotFoundError('Orden de venta no encontrada');
    }

    // Get lines
    const lines = await query<SalesOrderLine>(
      `SELECT sol.*,
              pr.name as product_name,
              um.name as uom_name
       FROM sales.sales_order_lines sol
       LEFT JOIN inventory.products pr ON sol.product_id = pr.id
       LEFT JOIN core.uom um ON sol.uom_id = um.id
       WHERE sol.order_id = $1
       ORDER BY sol.created_at`,
      [id]
    );

    order.lines = lines;

    return order;
  }

  async create(dto: CreateSalesOrderDto, tenantId: string, userId: string): Promise<SalesOrder> {
    // Generate sequence number using atomic database function
    const orderNumber = await sequencesService.getNextNumber(SEQUENCE_CODES.SALES_ORDER, tenantId);

    const orderDate = dto.order_date || new Date().toISOString().split('T')[0];

    const order = await queryOne<SalesOrder>(
      `INSERT INTO sales.sales_orders (
        tenant_id, company_id, name, client_order_ref, partner_id, order_date,
        validity_date, commitment_date, currency_id, pricelist_id, payment_term_id,
        user_id, sales_team_id, invoice_policy, notes, terms_conditions, created_by
      )
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
       RETURNING *`,
      [
        tenantId, dto.company_id, orderNumber, dto.client_order_ref, dto.partner_id,
        orderDate, dto.validity_date, dto.commitment_date, dto.currency_id,
        dto.pricelist_id, dto.payment_term_id, userId, dto.sales_team_id,
        dto.invoice_policy || 'order', dto.notes, dto.terms_conditions, userId
      ]
    );

    return order!;
  }

  async update(id: string, dto: UpdateSalesOrderDto, tenantId: string, userId: string): Promise<SalesOrder> {
    const existing = await this.findById(id, tenantId);

    if (existing.status !== 'draft') {
      throw new ValidationError('Solo se pueden editar órdenes en estado borrador');
    }

    const updateFields: string[] = [];
    const values: any[] = [];
    let paramIndex = 1;

    if (dto.partner_id !== undefined) {
      updateFields.push(`partner_id = $${paramIndex++}`);
      values.push(dto.partner_id);
    }
    if (dto.client_order_ref !== undefined) {
      updateFields.push(`client_order_ref = $${paramIndex++}`);
      values.push(dto.client_order_ref);
    }
    if (dto.order_date !== undefined) {
      updateFields.push(`order_date = $${paramIndex++}`);
      values.push(dto.order_date);
    }
    if (dto.validity_date !== undefined) {
      updateFields.push(`validity_date = $${paramIndex++}`);
      values.push(dto.validity_date);
    }
    if (dto.commitment_date !== undefined) {
      updateFields.push(`commitment_date = $${paramIndex++}`);
      values.push(dto.commitment_date);
    }
    if (dto.currency_id !== undefined) {
      updateFields.push(`currency_id = $${paramIndex++}`);
      values.push(dto.currency_id);
    }
    if (dto.pricelist_id !== undefined) {
      updateFields.push(`pricelist_id = $${paramIndex++}`);
      values.push(dto.pricelist_id);
    }
    if (dto.payment_term_id !== undefined) {
      updateFields.push(`payment_term_id = $${paramIndex++}`);
      values.push(dto.payment_term_id);
    }
    if (dto.sales_team_id !== undefined) {
      updateFields.push(`sales_team_id = $${paramIndex++}`);
      values.push(dto.sales_team_id);
    }
    if (dto.invoice_policy !== undefined) {
      updateFields.push(`invoice_policy = $${paramIndex++}`);
      values.push(dto.invoice_policy);
    }
    if (dto.notes !== undefined) {
      updateFields.push(`notes = $${paramIndex++}`);
      values.push(dto.notes);
    }
    if (dto.terms_conditions !== undefined) {
      updateFields.push(`terms_conditions = $${paramIndex++}`);
      values.push(dto.terms_conditions);
    }

    if (updateFields.length === 0) {
      return existing;
    }

    updateFields.push(`updated_by = $${paramIndex++}`);
    values.push(userId);
    updateFields.push(`updated_at = CURRENT_TIMESTAMP`);

    values.push(id, tenantId);

    await query(
      `UPDATE sales.sales_orders SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex++} AND tenant_id = $${paramIndex}`,
      values
    );

    return this.findById(id, tenantId);
  }

  async delete(id: string, tenantId: string): Promise<void> {
    const existing = await this.findById(id, tenantId);

    if (existing.status !== 'draft') {
      throw new ValidationError('Solo se pueden eliminar órdenes en estado borrador');
    }

    await query(
      `DELETE FROM sales.sales_orders WHERE id = $1 AND tenant_id = $2`,
      [id, tenantId]
    );
  }

  async addLine(orderId: string, dto: CreateSalesOrderLineDto, tenantId: string, userId: string): Promise<SalesOrderLine> {
    const order = await this.findById(orderId, tenantId);

    if (order.status !== 'draft') {
      throw new ValidationError('Solo se pueden agregar líneas a órdenes en estado borrador');
    }

    // Calculate amounts with taxes using taxesService
    const taxResult = await taxesService.calculateTaxes(
      {
        quantity: dto.quantity,
        priceUnit: dto.price_unit,
        discount: dto.discount || 0,
        taxIds: dto.tax_ids || [],
      },
      tenantId,
      'sales'
    );
    const amountUntaxed = taxResult.amountUntaxed;
    const amountTax = taxResult.amountTax;
    const amountTotal = taxResult.amountTotal;

    const line = await queryOne<SalesOrderLine>(
      `INSERT INTO sales.sales_order_lines (
        order_id, tenant_id, product_id, description, quantity, uom_id,
        price_unit, discount, tax_ids, amount_untaxed, amount_tax, amount_total, analytic_account_id
      )
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
       RETURNING *`,
      [
        orderId, tenantId, dto.product_id, dto.description, dto.quantity, dto.uom_id,
        dto.price_unit, dto.discount || 0, dto.tax_ids || [], amountUntaxed, amountTax, amountTotal, dto.analytic_account_id
      ]
    );

    // Update order totals
    await this.updateTotals(orderId);

    return line!;
  }

  async updateLine(orderId: string, lineId: string, dto: UpdateSalesOrderLineDto, tenantId: string): Promise<SalesOrderLine> {
    const order = await this.findById(orderId, tenantId);

    if (order.status !== 'draft') {
      throw new ValidationError('Solo se pueden editar líneas de órdenes en estado borrador');
    }

    const existingLine = order.lines?.find(l => l.id === lineId);
    if (!existingLine) {
      throw new NotFoundError('Línea de orden no encontrada');
    }

    const updateFields: string[] = [];
    const values: any[] = [];
    let paramIndex = 1;

    const quantity = dto.quantity ?? existingLine.quantity;
    const priceUnit = dto.price_unit ?? existingLine.price_unit;
    const discount = dto.discount ?? existingLine.discount;

    if (dto.description !== undefined) {
      updateFields.push(`description = $${paramIndex++}`);
      values.push(dto.description);
    }
    if (dto.quantity !== undefined) {
      updateFields.push(`quantity = $${paramIndex++}`);
      values.push(dto.quantity);
    }
    if (dto.uom_id !== undefined) {
      updateFields.push(`uom_id = $${paramIndex++}`);
      values.push(dto.uom_id);
    }
    if (dto.price_unit !== undefined) {
      updateFields.push(`price_unit = $${paramIndex++}`);
      values.push(dto.price_unit);
    }
    if (dto.discount !== undefined) {
      updateFields.push(`discount = $${paramIndex++}`);
      values.push(dto.discount);
    }
    if (dto.tax_ids !== undefined) {
      updateFields.push(`tax_ids = $${paramIndex++}`);
      values.push(dto.tax_ids);
    }
    if (dto.analytic_account_id !== undefined) {
      updateFields.push(`analytic_account_id = $${paramIndex++}`);
      values.push(dto.analytic_account_id);
    }

    // Recalculate amounts
    const subtotal = quantity * priceUnit;
    const discountAmount = subtotal * discount / 100;
    const amountUntaxed = subtotal - discountAmount;
    const amountTax = 0; // TODO: Calculate taxes
    const amountTotal = amountUntaxed + amountTax;

    updateFields.push(`amount_untaxed = $${paramIndex++}`);
    values.push(amountUntaxed);
    updateFields.push(`amount_tax = $${paramIndex++}`);
    values.push(amountTax);
    updateFields.push(`amount_total = $${paramIndex++}`);
    values.push(amountTotal);
    updateFields.push(`updated_at = CURRENT_TIMESTAMP`);

    values.push(lineId, orderId);

    await query(
      `UPDATE sales.sales_order_lines SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex++} AND order_id = $${paramIndex}`,
      values
    );

    // Update order totals
    await this.updateTotals(orderId);

    const updated = await queryOne<SalesOrderLine>(
      `SELECT * FROM sales.sales_order_lines WHERE id = $1`,
      [lineId]
    );

    return updated!;
  }

  async removeLine(orderId: string, lineId: string, tenantId: string): Promise<void> {
    const order = await this.findById(orderId, tenantId);

    if (order.status !== 'draft') {
      throw new ValidationError('Solo se pueden eliminar líneas de órdenes en estado borrador');
    }

    await query(
      `DELETE FROM sales.sales_order_lines WHERE id = $1 AND order_id = $2`,
      [lineId, orderId]
    );

    // Update order totals
    await this.updateTotals(orderId);
  }

  async confirm(id: string, tenantId: string, userId: string): Promise<SalesOrder> {
    const order = await this.findById(id, tenantId);

    if (order.status !== 'draft') {
      throw new ValidationError('Solo se pueden confirmar órdenes en estado borrador');
    }

    if (!order.lines || order.lines.length === 0) {
      throw new ValidationError('La orden debe tener al menos una línea');
    }

    const client = await getClient();
    try {
      await client.query('BEGIN');

      // Update order status to 'sent' (Odoo-compatible: quotation sent to customer)
      await client.query(
        `UPDATE sales.sales_orders SET
           status = 'sent',
           confirmed_at = CURRENT_TIMESTAMP,
           confirmed_by = $1,
           updated_by = $1,
           updated_at = CURRENT_TIMESTAMP
         WHERE id = $2`,
        [userId, id]
      );

      // Create delivery picking (optional - depends on business logic)
      // This would create an inventory.pickings record for delivery

      await client.query('COMMIT');

      return this.findById(id, tenantId);
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }

  async cancel(id: string, tenantId: string, userId: string): Promise<SalesOrder> {
    const order = await this.findById(id, tenantId);

    if (order.status === 'done') {
      throw new ValidationError('No se pueden cancelar órdenes completadas');
    }

    if (order.status === 'cancelled') {
      throw new ValidationError('La orden ya está cancelada');
    }

    // Check if there are any deliveries or invoices
    if (order.delivery_status !== 'pending') {
      throw new ValidationError('No se puede cancelar: ya hay entregas asociadas');
    }

    if (order.invoice_status !== 'pending') {
      throw new ValidationError('No se puede cancelar: ya hay facturas asociadas');
    }

    await query(
      `UPDATE sales.sales_orders SET
         status = 'cancelled',
         cancelled_at = CURRENT_TIMESTAMP,
         cancelled_by = $1,
         updated_by = $1,
         updated_at = CURRENT_TIMESTAMP
       WHERE id = $2 AND tenant_id = $3`,
      [userId, id, tenantId]
    );

    return this.findById(id, tenantId);
  }

  async createInvoice(id: string, tenantId: string, userId: string): Promise<{ orderId: string; invoiceId: string }> {
    const order = await this.findById(id, tenantId);

    if (order.status !== 'sent' && order.status !== 'sale' && order.status !== 'done') {
      throw new ValidationError('Solo se pueden facturar órdenes confirmadas (sent/sale)');
    }

    if (order.invoice_status === 'invoiced') {
      throw new ValidationError('La orden ya está completamente facturada');
    }

    // Check if there are quantities to invoice
    const linesToInvoice = order.lines?.filter(l => {
      if (order.invoice_policy === 'order') {
        return l.quantity > l.qty_invoiced;
      } else {
        return l.qty_delivered > l.qty_invoiced;
      }
    });

    if (!linesToInvoice || linesToInvoice.length === 0) {
      throw new ValidationError('No hay líneas para facturar');
    }

    const client = await getClient();
    try {
      await client.query('BEGIN');

      // Generate invoice number
      const seqResult = await client.query(
        `SELECT COALESCE(MAX(CAST(SUBSTRING(name FROM 5) AS INTEGER)), 0) + 1 as next_num
         FROM financial.invoices WHERE tenant_id = $1 AND name LIKE 'INV-%'`,
        [tenantId]
      );
      const invoiceNumber = `INV-${String(seqResult.rows[0]?.next_num || 1).padStart(6, '0')}`;

      // Create invoice
      const invoiceResult = await client.query(
        `INSERT INTO financial.invoices (
          tenant_id, company_id, name, partner_id, invoice_date, due_date,
          currency_id, invoice_type, amount_untaxed, amount_tax, amount_total,
          source_document, created_by
        )
         VALUES ($1, $2, $3, $4, CURRENT_DATE, CURRENT_DATE + INTERVAL '30 days',
                 $5, 'customer', 0, 0, 0, $6, $7)
         RETURNING id`,
        [tenantId, order.company_id, invoiceNumber, order.partner_id, order.currency_id, order.name, userId]
      );
      const invoiceId = invoiceResult.rows[0].id;

      // Create invoice lines and update qty_invoiced
      for (const line of linesToInvoice) {
        const qtyToInvoice = order.invoice_policy === 'order'
          ? line.quantity - line.qty_invoiced
          : line.qty_delivered - line.qty_invoiced;

        const lineAmount = qtyToInvoice * line.price_unit * (1 - line.discount / 100);

        await client.query(
          `INSERT INTO financial.invoice_lines (
            invoice_id, tenant_id, product_id, description, quantity, uom_id,
            price_unit, discount, amount_untaxed, amount_tax, amount_total
          )
           VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 0, $9)`,
          [invoiceId, tenantId, line.product_id, line.description, qtyToInvoice, line.uom_id, line.price_unit, line.discount, lineAmount]
        );

        await client.query(
          `UPDATE sales.sales_order_lines SET qty_invoiced = qty_invoiced + $1 WHERE id = $2`,
          [qtyToInvoice, line.id]
        );
      }

      // Update invoice totals
      await client.query(
        `UPDATE financial.invoices SET
           amount_untaxed = (SELECT COALESCE(SUM(amount_untaxed), 0) FROM financial.invoice_lines WHERE invoice_id = $1),
           amount_total = (SELECT COALESCE(SUM(amount_total), 0) FROM financial.invoice_lines WHERE invoice_id = $1)
         WHERE id = $1`,
        [invoiceId]
      );

      // Update order invoice_status
      await client.query(
        `UPDATE sales.sales_orders SET
           invoice_status = CASE
             WHEN (SELECT SUM(qty_invoiced) FROM sales.sales_order_lines WHERE order_id = $1) >=
                  (SELECT SUM(quantity) FROM sales.sales_order_lines WHERE order_id = $1)
             THEN 'invoiced'::sales.invoice_status
             ELSE 'partial'::sales.invoice_status
           END,
           updated_by = $2,
           updated_at = CURRENT_TIMESTAMP
         WHERE id = $1`,
        [id, userId]
      );

      await client.query('COMMIT');

      return { orderId: id, invoiceId };
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }

  private async updateTotals(orderId: string): Promise<void> {
    await query(
      `UPDATE sales.sales_orders SET
         amount_untaxed = COALESCE((SELECT SUM(amount_untaxed) FROM sales.sales_order_lines WHERE order_id = $1), 0),
         amount_tax = COALESCE((SELECT SUM(amount_tax) FROM sales.sales_order_lines WHERE order_id = $1), 0),
         amount_total = COALESCE((SELECT SUM(amount_total) FROM sales.sales_order_lines WHERE order_id = $1), 0)
       WHERE id = $1`,
      [orderId]
    );
  }
}

export const ordersService = new OrdersService();
