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

// ============================================================================
// TYPES
// ============================================================================

export type FiscalPeriodStatus = 'open' | 'closed';

export interface FiscalYear {
  id: string;
  tenant_id: string;
  company_id: string;
  name: string;
  code: string;
  date_from: Date;
  date_to: Date;
  status: FiscalPeriodStatus;
  created_at: Date;
}

export interface FiscalPeriod {
  id: string;
  tenant_id: string;
  fiscal_year_id: string;
  fiscal_year_name?: string;
  code: string;
  name: string;
  date_from: Date;
  date_to: Date;
  status: FiscalPeriodStatus;
  closed_at: Date | null;
  closed_by: string | null;
  closed_by_name?: string;
  created_at: Date;
}

export interface CreateFiscalYearDto {
  company_id: string;
  name: string;
  code: string;
  date_from: string;
  date_to: string;
}

export interface CreateFiscalPeriodDto {
  fiscal_year_id: string;
  code: string;
  name: string;
  date_from: string;
  date_to: string;
}

export interface FiscalPeriodFilters {
  company_id?: string;
  fiscal_year_id?: string;
  status?: FiscalPeriodStatus;
  date_from?: string;
  date_to?: string;
}

// ============================================================================
// SERVICE
// ============================================================================

class FiscalPeriodsService {
  // ==================== FISCAL YEARS ====================

  async findAllYears(tenantId: string, companyId?: string): Promise<FiscalYear[]> {
    let sql = `
      SELECT * FROM financial.fiscal_years
      WHERE tenant_id = $1
    `;
    const params: any[] = [tenantId];

    if (companyId) {
      sql += ` AND company_id = $2`;
      params.push(companyId);
    }

    sql += ` ORDER BY date_from DESC`;

    return query<FiscalYear>(sql, params);
  }

  async findYearById(id: string, tenantId: string): Promise<FiscalYear> {
    const year = await queryOne<FiscalYear>(
      `SELECT * FROM financial.fiscal_years WHERE id = $1 AND tenant_id = $2`,
      [id, tenantId]
    );

    if (!year) {
      throw new NotFoundError('Año fiscal no encontrado');
    }

    return year;
  }

  async createYear(dto: CreateFiscalYearDto, tenantId: string, userId: string): Promise<FiscalYear> {
    // Check for overlapping years
    const overlapping = await queryOne<{ id: string }>(
      `SELECT id FROM financial.fiscal_years
       WHERE tenant_id = $1 AND company_id = $2
         AND (date_from, date_to) OVERLAPS ($3::date, $4::date)`,
      [tenantId, dto.company_id, dto.date_from, dto.date_to]
    );

    if (overlapping) {
      throw new ConflictError('Ya existe un año fiscal que se superpone con estas fechas');
    }

    const year = await queryOne<FiscalYear>(
      `INSERT INTO financial.fiscal_years (
        tenant_id, company_id, name, code, date_from, date_to, created_by
      ) VALUES ($1, $2, $3, $4, $5, $6, $7)
      RETURNING *`,
      [tenantId, dto.company_id, dto.name, dto.code, dto.date_from, dto.date_to, userId]
    );

    logger.info('Fiscal year created', { yearId: year?.id, name: dto.name });

    return year!;
  }

  // ==================== FISCAL PERIODS ====================

  async findAllPeriods(tenantId: string, filters: FiscalPeriodFilters = {}): Promise<FiscalPeriod[]> {
    const conditions: string[] = ['fp.tenant_id = $1'];
    const params: any[] = [tenantId];
    let idx = 2;

    if (filters.fiscal_year_id) {
      conditions.push(`fp.fiscal_year_id = $${idx++}`);
      params.push(filters.fiscal_year_id);
    }

    if (filters.company_id) {
      conditions.push(`fy.company_id = $${idx++}`);
      params.push(filters.company_id);
    }

    if (filters.status) {
      conditions.push(`fp.status = $${idx++}`);
      params.push(filters.status);
    }

    if (filters.date_from) {
      conditions.push(`fp.date_from >= $${idx++}`);
      params.push(filters.date_from);
    }

    if (filters.date_to) {
      conditions.push(`fp.date_to <= $${idx++}`);
      params.push(filters.date_to);
    }

    return query<FiscalPeriod>(
      `SELECT fp.*,
              fy.name as fiscal_year_name,
              u.full_name as closed_by_name
       FROM financial.fiscal_periods fp
       JOIN financial.fiscal_years fy ON fp.fiscal_year_id = fy.id
       LEFT JOIN auth.users u ON fp.closed_by = u.id
       WHERE ${conditions.join(' AND ')}
       ORDER BY fp.date_from DESC`,
      params
    );
  }

  async findPeriodById(id: string, tenantId: string): Promise<FiscalPeriod> {
    const period = await queryOne<FiscalPeriod>(
      `SELECT fp.*,
              fy.name as fiscal_year_name,
              u.full_name as closed_by_name
       FROM financial.fiscal_periods fp
       JOIN financial.fiscal_years fy ON fp.fiscal_year_id = fy.id
       LEFT JOIN auth.users u ON fp.closed_by = u.id
       WHERE fp.id = $1 AND fp.tenant_id = $2`,
      [id, tenantId]
    );

    if (!period) {
      throw new NotFoundError('Período fiscal no encontrado');
    }

    return period;
  }

  async findPeriodByDate(date: Date, companyId: string, tenantId: string): Promise<FiscalPeriod | null> {
    return queryOne<FiscalPeriod>(
      `SELECT fp.*
       FROM financial.fiscal_periods fp
       JOIN financial.fiscal_years fy ON fp.fiscal_year_id = fy.id
       WHERE fp.tenant_id = $1
         AND fy.company_id = $2
         AND $3::date BETWEEN fp.date_from AND fp.date_to`,
      [tenantId, companyId, date]
    );
  }

  async createPeriod(dto: CreateFiscalPeriodDto, tenantId: string, userId: string): Promise<FiscalPeriod> {
    // Verify fiscal year exists
    await this.findYearById(dto.fiscal_year_id, tenantId);

    // Check for overlapping periods in the same year
    const overlapping = await queryOne<{ id: string }>(
      `SELECT id FROM financial.fiscal_periods
       WHERE tenant_id = $1 AND fiscal_year_id = $2
         AND (date_from, date_to) OVERLAPS ($3::date, $4::date)`,
      [tenantId, dto.fiscal_year_id, dto.date_from, dto.date_to]
    );

    if (overlapping) {
      throw new ConflictError('Ya existe un período que se superpone con estas fechas');
    }

    const period = await queryOne<FiscalPeriod>(
      `INSERT INTO financial.fiscal_periods (
        tenant_id, fiscal_year_id, code, name, date_from, date_to, created_by
      ) VALUES ($1, $2, $3, $4, $5, $6, $7)
      RETURNING *`,
      [tenantId, dto.fiscal_year_id, dto.code, dto.name, dto.date_from, dto.date_to, userId]
    );

    logger.info('Fiscal period created', { periodId: period?.id, name: dto.name });

    return period!;
  }

  // ==================== PERIOD OPERATIONS ====================

  /**
   * Close a fiscal period
   * Uses database function for validation
   */
  async closePeriod(periodId: string, tenantId: string, userId: string): Promise<FiscalPeriod> {
    // Verify period exists and belongs to tenant
    await this.findPeriodById(periodId, tenantId);

    // Use database function for atomic close with validations
    const result = await queryOne<FiscalPeriod>(
      `SELECT * FROM financial.close_fiscal_period($1, $2)`,
      [periodId, userId]
    );

    if (!result) {
      throw new Error('Error al cerrar período');
    }

    logger.info('Fiscal period closed', { periodId, userId });

    return result;
  }

  /**
   * Reopen a fiscal period (admin only)
   */
  async reopenPeriod(periodId: string, tenantId: string, userId: string, reason?: string): Promise<FiscalPeriod> {
    // Verify period exists and belongs to tenant
    await this.findPeriodById(periodId, tenantId);

    // Use database function for atomic reopen with audit
    const result = await queryOne<FiscalPeriod>(
      `SELECT * FROM financial.reopen_fiscal_period($1, $2, $3)`,
      [periodId, userId, reason]
    );

    if (!result) {
      throw new Error('Error al reabrir período');
    }

    logger.warn('Fiscal period reopened', { periodId, userId, reason });

    return result;
  }

  /**
   * Get statistics for a period
   */
  async getPeriodStats(periodId: string, tenantId: string): Promise<{
    total_entries: number;
    draft_entries: number;
    posted_entries: number;
    total_debit: number;
    total_credit: number;
  }> {
    const stats = await queryOne<{
      total_entries: string;
      draft_entries: string;
      posted_entries: string;
      total_debit: string;
      total_credit: string;
    }>(
      `SELECT
         COUNT(*) as total_entries,
         COUNT(*) FILTER (WHERE status = 'draft') as draft_entries,
         COUNT(*) FILTER (WHERE status = 'posted') as posted_entries,
         COALESCE(SUM(total_debit), 0) as total_debit,
         COALESCE(SUM(total_credit), 0) as total_credit
       FROM financial.journal_entries
       WHERE fiscal_period_id = $1 AND tenant_id = $2`,
      [periodId, tenantId]
    );

    return {
      total_entries: parseInt(stats?.total_entries || '0', 10),
      draft_entries: parseInt(stats?.draft_entries || '0', 10),
      posted_entries: parseInt(stats?.posted_entries || '0', 10),
      total_debit: parseFloat(stats?.total_debit || '0'),
      total_credit: parseFloat(stats?.total_credit || '0'),
    };
  }

  /**
   * Generate monthly periods for a fiscal year
   */
  async generateMonthlyPeriods(fiscalYearId: string, tenantId: string, userId: string): Promise<FiscalPeriod[]> {
    const year = await this.findYearById(fiscalYearId, tenantId);

    const startDate = new Date(year.date_from);
    const endDate = new Date(year.date_to);
    const periods: FiscalPeriod[] = [];

    let currentDate = new Date(startDate);
    let periodNum = 1;

    while (currentDate <= endDate) {
      const periodStart = new Date(currentDate);
      const periodEnd = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0);

      // Don't exceed the fiscal year end
      if (periodEnd > endDate) {
        periodEnd.setTime(endDate.getTime());
      }

      const monthNames = [
        'Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
        'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'
      ];

      try {
        const period = await this.createPeriod({
          fiscal_year_id: fiscalYearId,
          code: String(periodNum).padStart(2, '0'),
          name: `${monthNames[periodStart.getMonth()]} ${periodStart.getFullYear()}`,
          date_from: periodStart.toISOString().split('T')[0],
          date_to: periodEnd.toISOString().split('T')[0],
        }, tenantId, userId);

        periods.push(period);
      } catch (error) {
        // Skip if period already exists (overlapping check will fail)
        logger.debug('Period creation skipped', { periodNum, error });
      }

      // Move to next month
      currentDate.setMonth(currentDate.getMonth() + 1);
      currentDate.setDate(1);
      periodNum++;
    }

    logger.info('Generated monthly periods', { fiscalYearId, count: periods.length });

    return periods;
  }
}

export const fiscalPeriodsService = new FiscalPeriodsService();
