import { query, queryOne } from '../../config/database.js';
import { NotFoundError, ConflictError } from '../../shared/errors/index.js';

export type PartnerType = 'person' | 'company';

export interface Partner {
  id: string;
  tenant_id: string;
  name: string;
  legal_name?: string;
  partner_type: PartnerType;
  is_customer: boolean;
  is_supplier: boolean;
  is_employee: boolean;
  is_company: boolean;
  email?: string;
  phone?: string;
  mobile?: string;
  website?: string;
  tax_id?: string;
  company_id?: string;
  parent_id?: string;
  currency_id?: string;
  notes?: string;
  active: boolean;
  created_at: Date;
  created_by?: string;
}

export interface CreatePartnerDto {
  name: string;
  legal_name?: string;
  partner_type?: PartnerType;
  is_customer?: boolean;
  is_supplier?: boolean;
  is_employee?: boolean;
  is_company?: boolean;
  email?: string;
  phone?: string;
  mobile?: string;
  website?: string;
  tax_id?: string;
  company_id?: string;
  parent_id?: string;
  currency_id?: string;
  notes?: string;
}

export interface UpdatePartnerDto {
  name?: string;
  legal_name?: string | null;
  is_customer?: boolean;
  is_supplier?: boolean;
  is_employee?: boolean;
  email?: string | null;
  phone?: string | null;
  mobile?: string | null;
  website?: string | null;
  tax_id?: string | null;
  company_id?: string | null;
  parent_id?: string | null;
  currency_id?: string | null;
  notes?: string | null;
  active?: boolean;
}

export interface PartnerFilters {
  search?: string;
  is_customer?: boolean;
  is_supplier?: boolean;
  is_employee?: boolean;
  company_id?: string;
  active?: boolean;
  page?: number;
  limit?: number;
}

class PartnersService {
  async findAll(tenantId: string, filters: PartnerFilters = {}): Promise<{ data: Partner[]; total: number }> {
    const { search, is_customer, is_supplier, is_employee, company_id, active, page = 1, limit = 20 } = filters;
    const offset = (page - 1) * limit;

    let whereClause = 'WHERE p.tenant_id = $1 AND p.deleted_at IS NULL';
    const params: any[] = [tenantId];
    let paramIndex = 2;

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

    if (is_customer !== undefined) {
      whereClause += ` AND p.is_customer = $${paramIndex++}`;
      params.push(is_customer);
    }

    if (is_supplier !== undefined) {
      whereClause += ` AND p.is_supplier = $${paramIndex++}`;
      params.push(is_supplier);
    }

    if (is_employee !== undefined) {
      whereClause += ` AND p.is_employee = $${paramIndex++}`;
      params.push(is_employee);
    }

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

    if (active !== undefined) {
      whereClause += ` AND p.active = $${paramIndex++}`;
      params.push(active);
    }

    const countResult = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count FROM core.partners p ${whereClause}`,
      params
    );

    params.push(limit, offset);
    const data = await query<Partner>(
      `SELECT p.*,
              c.name as company_name,
              cur.code as currency_code,
              pp.name as parent_name
       FROM core.partners p
       LEFT JOIN auth.companies c ON p.company_id = c.id
       LEFT JOIN core.currencies cur ON p.currency_id = cur.id
       LEFT JOIN core.partners pp ON p.parent_id = pp.id
       ${whereClause}
       ORDER BY p.name ASC
       LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
      params
    );

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

  async findById(id: string, tenantId: string): Promise<Partner> {
    const partner = await queryOne<Partner>(
      `SELECT p.*,
              c.name as company_name,
              cur.code as currency_code,
              pp.name as parent_name
       FROM core.partners p
       LEFT JOIN auth.companies c ON p.company_id = c.id
       LEFT JOIN core.currencies cur ON p.currency_id = cur.id
       LEFT JOIN core.partners pp ON p.parent_id = pp.id
       WHERE p.id = $1 AND p.tenant_id = $2 AND p.deleted_at IS NULL`,
      [id, tenantId]
    );

    if (!partner) {
      throw new NotFoundError('Contacto no encontrado');
    }

    return partner;
  }

  async create(dto: CreatePartnerDto, tenantId: string, userId: string): Promise<Partner> {
    // Validate parent partner exists
    if (dto.parent_id) {
      const parent = await queryOne<Partner>(
        `SELECT id FROM core.partners WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL`,
        [dto.parent_id, tenantId]
      );
      if (!parent) {
        throw new NotFoundError('Contacto padre no encontrado');
      }
    }

    const partner = await queryOne<Partner>(
      `INSERT INTO core.partners (
        tenant_id, name, legal_name, partner_type, is_customer, is_supplier,
        is_employee, is_company, email, phone, mobile, website, tax_id,
        company_id, parent_id, currency_id, notes, created_by
      )
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)
       RETURNING *`,
      [
        tenantId,
        dto.name,
        dto.legal_name,
        dto.partner_type || 'person',
        dto.is_customer || false,
        dto.is_supplier || false,
        dto.is_employee || false,
        dto.is_company || false,
        dto.email?.toLowerCase(),
        dto.phone,
        dto.mobile,
        dto.website,
        dto.tax_id,
        dto.company_id,
        dto.parent_id,
        dto.currency_id,
        dto.notes,
        userId,
      ]
    );

    return partner!;
  }

  async update(id: string, dto: UpdatePartnerDto, tenantId: string, userId: string): Promise<Partner> {
    await this.findById(id, tenantId);

    // Validate parent (prevent self-reference)
    if (dto.parent_id) {
      if (dto.parent_id === id) {
        throw new ConflictError('Un contacto no puede ser su propio padre');
      }
      const parent = await queryOne<Partner>(
        `SELECT id FROM core.partners WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL`,
        [dto.parent_id, tenantId]
      );
      if (!parent) {
        throw new NotFoundError('Contacto padre no encontrado');
      }
    }

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

    if (dto.name !== undefined) {
      updateFields.push(`name = $${paramIndex++}`);
      values.push(dto.name);
    }
    if (dto.legal_name !== undefined) {
      updateFields.push(`legal_name = $${paramIndex++}`);
      values.push(dto.legal_name);
    }
    if (dto.is_customer !== undefined) {
      updateFields.push(`is_customer = $${paramIndex++}`);
      values.push(dto.is_customer);
    }
    if (dto.is_supplier !== undefined) {
      updateFields.push(`is_supplier = $${paramIndex++}`);
      values.push(dto.is_supplier);
    }
    if (dto.is_employee !== undefined) {
      updateFields.push(`is_employee = $${paramIndex++}`);
      values.push(dto.is_employee);
    }
    if (dto.email !== undefined) {
      updateFields.push(`email = $${paramIndex++}`);
      values.push(dto.email?.toLowerCase());
    }
    if (dto.phone !== undefined) {
      updateFields.push(`phone = $${paramIndex++}`);
      values.push(dto.phone);
    }
    if (dto.mobile !== undefined) {
      updateFields.push(`mobile = $${paramIndex++}`);
      values.push(dto.mobile);
    }
    if (dto.website !== undefined) {
      updateFields.push(`website = $${paramIndex++}`);
      values.push(dto.website);
    }
    if (dto.tax_id !== undefined) {
      updateFields.push(`tax_id = $${paramIndex++}`);
      values.push(dto.tax_id);
    }
    if (dto.company_id !== undefined) {
      updateFields.push(`company_id = $${paramIndex++}`);
      values.push(dto.company_id);
    }
    if (dto.parent_id !== undefined) {
      updateFields.push(`parent_id = $${paramIndex++}`);
      values.push(dto.parent_id);
    }
    if (dto.currency_id !== undefined) {
      updateFields.push(`currency_id = $${paramIndex++}`);
      values.push(dto.currency_id);
    }
    if (dto.notes !== undefined) {
      updateFields.push(`notes = $${paramIndex++}`);
      values.push(dto.notes);
    }
    if (dto.active !== undefined) {
      updateFields.push(`active = $${paramIndex++}`);
      values.push(dto.active);
    }

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

    values.push(id, tenantId);

    const partner = await queryOne<Partner>(
      `UPDATE core.partners
       SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex++} AND tenant_id = $${paramIndex} AND deleted_at IS NULL
       RETURNING *`,
      values
    );

    return partner!;
  }

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

    // Check if has child contacts
    const children = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count FROM core.partners
       WHERE parent_id = $1 AND tenant_id = $2 AND deleted_at IS NULL`,
      [id, tenantId]
    );

    if (parseInt(children?.count || '0', 10) > 0) {
      throw new ConflictError('No se puede eliminar un contacto que tiene contactos relacionados');
    }

    // Soft delete
    await query(
      `UPDATE core.partners
       SET deleted_at = CURRENT_TIMESTAMP, deleted_by = $1, active = false
       WHERE id = $2 AND tenant_id = $3`,
      [userId, id, tenantId]
    );
  }

  // Get customers only
  async findCustomers(tenantId: string, filters: Omit<PartnerFilters, 'is_customer'>): Promise<{ data: Partner[]; total: number }> {
    return this.findAll(tenantId, { ...filters, is_customer: true });
  }

  // Get suppliers only
  async findSuppliers(tenantId: string, filters: Omit<PartnerFilters, 'is_supplier'>): Promise<{ data: Partner[]; total: number }> {
    return this.findAll(tenantId, { ...filters, is_supplier: true });
  }
}

export const partnersService = new PartnersService();
