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

export type JournalType = 'sale' | 'purchase' | 'cash' | 'bank' | 'general';

export interface Journal {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  name: string;
  code: string;
  journal_type: JournalType;
  default_account_id?: string;
  default_account_name?: string;
  sequence_id?: string;
  currency_id?: string;
  currency_code?: string;
  active: boolean;
  created_at: Date;
}

export interface CreateJournalDto {
  company_id: string;
  name: string;
  code: string;
  journal_type: JournalType;
  default_account_id?: string;
  sequence_id?: string;
  currency_id?: string;
}

export interface UpdateJournalDto {
  name?: string;
  default_account_id?: string | null;
  sequence_id?: string | null;
  currency_id?: string | null;
  active?: boolean;
}

export interface JournalFilters {
  company_id?: string;
  journal_type?: JournalType;
  active?: boolean;
  page?: number;
  limit?: number;
}

class JournalsService {
  async findAll(tenantId: string, filters: JournalFilters = {}): Promise<{ data: Journal[]; total: number }> {
    const { company_id, journal_type, active, page = 1, limit = 50 } = filters;
    const offset = (page - 1) * limit;

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

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

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

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

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

    params.push(limit, offset);
    const data = await query<Journal>(
      `SELECT j.*,
              c.name as company_name,
              a.name as default_account_name,
              cur.code as currency_code
       FROM financial.journals j
       LEFT JOIN auth.companies c ON j.company_id = c.id
       LEFT JOIN financial.accounts a ON j.default_account_id = a.id
       LEFT JOIN core.currencies cur ON j.currency_id = cur.id
       ${whereClause}
       ORDER BY j.code
       LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
      params
    );

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

  async findById(id: string, tenantId: string): Promise<Journal> {
    const journal = await queryOne<Journal>(
      `SELECT j.*,
              c.name as company_name,
              a.name as default_account_name,
              cur.code as currency_code
       FROM financial.journals j
       LEFT JOIN auth.companies c ON j.company_id = c.id
       LEFT JOIN financial.accounts a ON j.default_account_id = a.id
       LEFT JOIN core.currencies cur ON j.currency_id = cur.id
       WHERE j.id = $1 AND j.tenant_id = $2 AND j.deleted_at IS NULL`,
      [id, tenantId]
    );

    if (!journal) {
      throw new NotFoundError('Diario no encontrado');
    }

    return journal;
  }

  async create(dto: CreateJournalDto, tenantId: string, userId: string): Promise<Journal> {
    // Validate unique code within company
    const existing = await queryOne<Journal>(
      `SELECT id FROM financial.journals WHERE company_id = $1 AND code = $2 AND deleted_at IS NULL`,
      [dto.company_id, dto.code]
    );
    if (existing) {
      throw new ConflictError(`Ya existe un diario con código ${dto.code}`);
    }

    const journal = await queryOne<Journal>(
      `INSERT INTO financial.journals (tenant_id, company_id, name, code, journal_type, default_account_id, sequence_id, currency_id, created_by)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
       RETURNING *`,
      [
        tenantId,
        dto.company_id,
        dto.name,
        dto.code,
        dto.journal_type,
        dto.default_account_id,
        dto.sequence_id,
        dto.currency_id,
        userId,
      ]
    );

    return journal!;
  }

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

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

    if (dto.name !== undefined) {
      updateFields.push(`name = $${paramIndex++}`);
      values.push(dto.name);
    }
    if (dto.default_account_id !== undefined) {
      updateFields.push(`default_account_id = $${paramIndex++}`);
      values.push(dto.default_account_id);
    }
    if (dto.sequence_id !== undefined) {
      updateFields.push(`sequence_id = $${paramIndex++}`);
      values.push(dto.sequence_id);
    }
    if (dto.currency_id !== undefined) {
      updateFields.push(`currency_id = $${paramIndex++}`);
      values.push(dto.currency_id);
    }
    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 journal = await queryOne<Journal>(
      `UPDATE financial.journals
       SET ${updateFields.join(', ')}
       WHERE id = $${paramIndex++} AND tenant_id = $${paramIndex} AND deleted_at IS NULL
       RETURNING *`,
      values
    );

    return journal!;
  }

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

    // Check if journal has entries
    const entries = await queryOne<{ count: string }>(
      `SELECT COUNT(*) as count FROM financial.journal_entries WHERE journal_id = $1`,
      [id]
    );
    if (parseInt(entries?.count || '0', 10) > 0) {
      throw new ConflictError('No se puede eliminar un diario que tiene pólizas');
    }

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

export const journalsService = new JournalsService();
