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

export interface Company {
  id: string;
  tenant_id: string;
  name: string;
  legal_name?: string;
  tax_id?: string;
  currency_id?: string;
  parent_company_id?: string;
  settings?: Record<string, any>;
  created_at: Date;
  created_by?: string;
  updated_at?: Date;
  updated_by?: string;
}

export interface CreateCompanyDto {
  name: string;
  legal_name?: string;
  tax_id?: string;
  currency_id?: string;
  parent_company_id?: string;
  settings?: Record<string, any>;
}

export interface UpdateCompanyDto {
  name?: string;
  legal_name?: string | null;
  tax_id?: string | null;
  currency_id?: string | null;
  parent_company_id?: string | null;
  settings?: Record<string, any>;
}

export interface CompanyFilters {
  search?: string;
  parent_company_id?: string;
  page?: number;
  limit?: number;
}

class CompaniesService {
  async findAll(tenantId: string, filters: CompanyFilters = {}): Promise<{ data: Company[]; total: number }> {
    const { search, parent_company_id, page = 1, limit = 20 } = filters;
    const offset = (page - 1) * limit;

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

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

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

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

    params.push(limit, offset);
    const data = await query<Company>(
      `SELECT c.*,
              cur.code as currency_code,
              pc.name as parent_company_name
       FROM auth.companies c
       LEFT JOIN core.currencies cur ON c.currency_id = cur.id
       LEFT JOIN auth.companies pc ON c.parent_company_id = pc.id
       ${whereClause}
       ORDER BY c.name ASC
       LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
      params
    );

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

  async findById(id: string, tenantId: string): Promise<Company> {
    const company = await queryOne<Company>(
      `SELECT c.*,
              cur.code as currency_code,
              pc.name as parent_company_name
       FROM auth.companies c
       LEFT JOIN core.currencies cur ON c.currency_id = cur.id
       LEFT JOIN auth.companies pc ON c.parent_company_id = pc.id
       WHERE c.id = $1 AND c.tenant_id = $2 AND c.deleted_at IS NULL`,
      [id, tenantId]
    );

    if (!company) {
      throw new NotFoundError('Empresa no encontrada');
    }

    return company;
  }

  async create(dto: CreateCompanyDto, tenantId: string, userId: string): Promise<Company> {
    // Validate unique tax_id within tenant
    if (dto.tax_id) {
      const existing = await queryOne<Company>(
        `SELECT id FROM auth.companies
         WHERE tenant_id = $1 AND tax_id = $2 AND deleted_at IS NULL`,
        [tenantId, dto.tax_id]
      );
      if (existing) {
        throw new ConflictError('Ya existe una empresa con este RFC');
      }
    }

    // Validate parent company exists
    if (dto.parent_company_id) {
      const parent = await queryOne<Company>(
        `SELECT id FROM auth.companies
         WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL`,
        [dto.parent_company_id, tenantId]
      );
      if (!parent) {
        throw new NotFoundError('Empresa matriz no encontrada');
      }
    }

    const company = await queryOne<Company>(
      `INSERT INTO auth.companies (tenant_id, name, legal_name, tax_id, currency_id, parent_company_id, settings, created_by)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
       RETURNING *`,
      [
        tenantId,
        dto.name,
        dto.legal_name,
        dto.tax_id,
        dto.currency_id,
        dto.parent_company_id,
        JSON.stringify(dto.settings || {}),
        userId,
      ]
    );

    return company!;
  }

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

    // Validate unique tax_id
    if (dto.tax_id && dto.tax_id !== existing.tax_id) {
      const duplicate = await queryOne<Company>(
        `SELECT id FROM auth.companies
         WHERE tenant_id = $1 AND tax_id = $2 AND id != $3 AND deleted_at IS NULL`,
        [tenantId, dto.tax_id, id]
      );
      if (duplicate) {
        throw new ConflictError('Ya existe una empresa con este RFC');
      }
    }

    // Validate parent company (prevent self-reference and cycles)
    if (dto.parent_company_id) {
      if (dto.parent_company_id === id) {
        throw new ConflictError('Una empresa no puede ser su propia matriz');
      }
      const parent = await queryOne<Company>(
        `SELECT id FROM auth.companies
         WHERE id = $1 AND tenant_id = $2 AND deleted_at IS NULL`,
        [dto.parent_company_id, tenantId]
      );
      if (!parent) {
        throw new NotFoundError('Empresa matriz no encontrada');
      }
    }

    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.tax_id !== undefined) {
      updateFields.push(`tax_id = $${paramIndex++}`);
      values.push(dto.tax_id);
    }
    if (dto.currency_id !== undefined) {
      updateFields.push(`currency_id = $${paramIndex++}`);
      values.push(dto.currency_id);
    }
    if (dto.parent_company_id !== undefined) {
      updateFields.push(`parent_company_id = $${paramIndex++}`);
      values.push(dto.parent_company_id);
    }
    if (dto.settings !== undefined) {
      updateFields.push(`settings = $${paramIndex++}`);
      values.push(JSON.stringify(dto.settings));
    }

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

    values.push(id, tenantId);

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

    return company!;
  }

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

    // Check if company has child companies
    const children = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count FROM auth.companies
       WHERE parent_company_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 una empresa que tiene empresas subsidiarias');
    }

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

  async getUsers(companyId: string, tenantId: string): Promise<any[]> {
    await this.findById(companyId, tenantId);

    return query(
      `SELECT u.id, u.email, u.full_name, u.status, uc.is_default, uc.assigned_at
       FROM auth.users u
       INNER JOIN auth.user_companies uc ON u.id = uc.user_id
       WHERE uc.company_id = $1 AND u.tenant_id = $2 AND u.deleted_at IS NULL
       ORDER BY u.full_name`,
      [companyId, tenantId]
    );
  }
}

export const companiesService = new CompaniesService();
