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

export type LeadStatus = 'new' | 'contacted' | 'qualified' | 'converted' | 'lost';
export type LeadSource = 'website' | 'phone' | 'email' | 'referral' | 'social_media' | 'advertising' | 'event' | 'other';

export interface Lead {
  id: string;
  tenant_id: string;
  company_id: string;
  company_name?: string;
  name: string;
  ref?: string;
  contact_name?: string;
  email?: string;
  phone?: string;
  mobile?: string;
  website?: string;
  company_prospect_name?: string;
  job_position?: string;
  industry?: string;
  employee_count?: string;
  annual_revenue?: number;
  street?: string;
  city?: string;
  state?: string;
  zip?: string;
  country?: string;
  stage_id?: string;
  stage_name?: string;
  status: LeadStatus;
  user_id?: string;
  user_name?: string;
  sales_team_id?: string;
  source?: LeadSource;
  priority: number;
  probability: number;
  expected_revenue?: number;
  date_open?: Date;
  date_closed?: Date;
  date_deadline?: Date;
  date_last_activity?: Date;
  partner_id?: string;
  opportunity_id?: string;
  lost_reason_id?: string;
  lost_reason_name?: string;
  lost_notes?: string;
  description?: string;
  notes?: string;
  tags?: string[];
  created_at: Date;
}

export interface CreateLeadDto {
  company_id: string;
  name: string;
  ref?: string;
  contact_name?: string;
  email?: string;
  phone?: string;
  mobile?: string;
  website?: string;
  company_prospect_name?: string;
  job_position?: string;
  industry?: string;
  employee_count?: string;
  annual_revenue?: number;
  street?: string;
  city?: string;
  state?: string;
  zip?: string;
  country?: string;
  stage_id?: string;
  user_id?: string;
  sales_team_id?: string;
  source?: LeadSource;
  priority?: number;
  probability?: number;
  expected_revenue?: number;
  date_deadline?: string;
  description?: string;
  notes?: string;
  tags?: string[];
}

export interface UpdateLeadDto {
  name?: string;
  ref?: string | null;
  contact_name?: string | null;
  email?: string | null;
  phone?: string | null;
  mobile?: string | null;
  website?: string | null;
  company_prospect_name?: string | null;
  job_position?: string | null;
  industry?: string | null;
  employee_count?: string | null;
  annual_revenue?: number | null;
  street?: string | null;
  city?: string | null;
  state?: string | null;
  zip?: string | null;
  country?: string | null;
  stage_id?: string | null;
  user_id?: string | null;
  sales_team_id?: string | null;
  source?: LeadSource | null;
  priority?: number;
  probability?: number;
  expected_revenue?: number | null;
  date_deadline?: string | null;
  description?: string | null;
  notes?: string | null;
  tags?: string[] | null;
}

export interface LeadFilters {
  company_id?: string;
  status?: LeadStatus;
  stage_id?: string;
  user_id?: string;
  source?: LeadSource;
  priority?: number;
  search?: string;
  page?: number;
  limit?: number;
}

class LeadsService {
  async findAll(tenantId: string, filters: LeadFilters = {}): Promise<{ data: Lead[]; total: number }> {
    const { company_id, status, stage_id, user_id, source, priority, search, page = 1, limit = 20 } = filters;
    const offset = (page - 1) * limit;

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

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

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

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

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

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

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

    if (search) {
      whereClause += ` AND (l.name ILIKE $${paramIndex} OR l.contact_name ILIKE $${paramIndex} OR l.email ILIKE $${paramIndex} OR l.company_name ILIKE $${paramIndex})`;
      params.push(`%${search}%`);
      paramIndex++;
    }

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

    params.push(limit, offset);
    const data = await query<Lead>(
      `SELECT l.*,
              c.name as company_org_name,
              ls.name as stage_name,
              u.email as user_email,
              lr.name as lost_reason_name
       FROM crm.leads l
       LEFT JOIN auth.companies c ON l.company_id = c.id
       LEFT JOIN crm.lead_stages ls ON l.stage_id = ls.id
       LEFT JOIN auth.users u ON l.user_id = u.id
       LEFT JOIN crm.lost_reasons lr ON l.lost_reason_id = lr.id
       ${whereClause}
       ORDER BY l.priority DESC, l.created_at DESC
       LIMIT $${paramIndex} OFFSET $${paramIndex + 1}`,
      params
    );

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

  async findById(id: string, tenantId: string): Promise<Lead> {
    const lead = await queryOne<Lead>(
      `SELECT l.*,
              c.name as company_org_name,
              ls.name as stage_name,
              u.email as user_email,
              lr.name as lost_reason_name
       FROM crm.leads l
       LEFT JOIN auth.companies c ON l.company_id = c.id
       LEFT JOIN crm.lead_stages ls ON l.stage_id = ls.id
       LEFT JOIN auth.users u ON l.user_id = u.id
       LEFT JOIN crm.lost_reasons lr ON l.lost_reason_id = lr.id
       WHERE l.id = $1 AND l.tenant_id = $2`,
      [id, tenantId]
    );

    if (!lead) {
      throw new NotFoundError('Lead no encontrado');
    }

    return lead;
  }

  async create(dto: CreateLeadDto, tenantId: string, userId: string): Promise<Lead> {
    const lead = await queryOne<Lead>(
      `INSERT INTO crm.leads (
        tenant_id, company_id, name, ref, contact_name, email, phone, mobile, website,
        company_name, job_position, industry, employee_count, annual_revenue,
        street, city, state, zip, country, stage_id, user_id, sales_team_id, source,
        priority, probability, expected_revenue, date_deadline, description, notes, tags,
        date_open, created_by
      )
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17,
               $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, CURRENT_TIMESTAMP, $31)
       RETURNING *`,
      [
        tenantId, dto.company_id, dto.name, dto.ref, dto.contact_name, dto.email, dto.phone,
        dto.mobile, dto.website, dto.company_prospect_name, dto.job_position, dto.industry,
        dto.employee_count, dto.annual_revenue, dto.street, dto.city, dto.state, dto.zip,
        dto.country, dto.stage_id, dto.user_id, dto.sales_team_id, dto.source,
        dto.priority || 0, dto.probability || 0, dto.expected_revenue, dto.date_deadline,
        dto.description, dto.notes, dto.tags, userId
      ]
    );

    return this.findById(lead!.id, tenantId);
  }

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

    if (existing.status === 'converted' || existing.status === 'lost') {
      throw new ValidationError('No se puede editar un lead convertido o perdido');
    }

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

    const fieldsToUpdate = [
      'name', 'ref', 'contact_name', 'email', 'phone', 'mobile', 'website',
      'company_prospect_name', 'job_position', 'industry', 'employee_count', 'annual_revenue',
      'street', 'city', 'state', 'zip', 'country', 'stage_id', 'user_id', 'sales_team_id',
      'source', 'priority', 'probability', 'expected_revenue', 'date_deadline',
      'description', 'notes', 'tags'
    ];

    for (const field of fieldsToUpdate) {
      const key = field === 'company_prospect_name' ? 'company_name' : field;
      if ((dto as any)[field] !== undefined) {
        updateFields.push(`${key} = $${paramIndex++}`);
        values.push((dto as any)[field]);
      }
    }

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

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

    values.push(id, tenantId);

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

    return this.findById(id, tenantId);
  }

  async moveStage(id: string, stageId: string, tenantId: string, userId: string): Promise<Lead> {
    const lead = await this.findById(id, tenantId);

    if (lead.status === 'converted' || lead.status === 'lost') {
      throw new ValidationError('No se puede mover un lead convertido o perdido');
    }

    await query(
      `UPDATE crm.leads SET
         stage_id = $1,
         date_last_activity = CURRENT_TIMESTAMP,
         updated_by = $2,
         updated_at = CURRENT_TIMESTAMP
       WHERE id = $3 AND tenant_id = $4`,
      [stageId, userId, id, tenantId]
    );

    return this.findById(id, tenantId);
  }

  async convert(id: string, tenantId: string, userId: string): Promise<{ lead: Lead; opportunity_id: string }> {
    const lead = await this.findById(id, tenantId);

    if (lead.status === 'converted') {
      throw new ValidationError('El lead ya fue convertido');
    }

    if (lead.status === 'lost') {
      throw new ValidationError('No se puede convertir un lead perdido');
    }

    const client = await getClient();

    try {
      await client.query('BEGIN');

      // Create or get partner
      let partnerId = lead.partner_id;

      if (!partnerId && lead.email) {
        // Check if partner exists with same email
        const existingPartner = await client.query(
          `SELECT id FROM core.partners WHERE email = $1 AND tenant_id = $2`,
          [lead.email, tenantId]
        );

        if (existingPartner.rows.length > 0) {
          partnerId = existingPartner.rows[0].id;
        } else {
          // Create new partner
          const partnerResult = await client.query(
            `INSERT INTO core.partners (tenant_id, name, email, phone, mobile, is_customer, created_by)
             VALUES ($1, $2, $3, $4, $5, TRUE, $6)
             RETURNING id`,
            [tenantId, lead.contact_name || lead.name, lead.email, lead.phone, lead.mobile, userId]
          );
          partnerId = partnerResult.rows[0].id;
        }
      }

      if (!partnerId) {
        throw new ValidationError('El lead debe tener un email o partner asociado para convertirse');
      }

      // Get default opportunity stage
      const stageResult = await client.query(
        `SELECT id FROM crm.opportunity_stages WHERE tenant_id = $1 ORDER BY sequence LIMIT 1`,
        [tenantId]
      );

      const stageId = stageResult.rows[0]?.id || null;

      // Create opportunity
      const opportunityResult = await client.query(
        `INSERT INTO crm.opportunities (
          tenant_id, company_id, name, partner_id, contact_name, email, phone,
          stage_id, user_id, sales_team_id, source, priority, probability,
          expected_revenue, lead_id, description, notes, tags, created_by
        )
         VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
         RETURNING id`,
        [
          tenantId, lead.company_id, lead.name, partnerId, lead.contact_name, lead.email,
          lead.phone, stageId, lead.user_id, lead.sales_team_id, lead.source, lead.priority,
          lead.probability, lead.expected_revenue, id, lead.description, lead.notes, lead.tags, userId
        ]
      );
      const opportunityId = opportunityResult.rows[0].id;

      // Update lead
      await client.query(
        `UPDATE crm.leads SET
           status = 'converted',
           partner_id = $1,
           opportunity_id = $2,
           date_closed = CURRENT_TIMESTAMP,
           updated_by = $3,
           updated_at = CURRENT_TIMESTAMP
         WHERE id = $4`,
        [partnerId, opportunityId, userId, id]
      );

      await client.query('COMMIT');

      const updatedLead = await this.findById(id, tenantId);

      return { lead: updatedLead, opportunity_id: opportunityId };
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }

  async markLost(id: string, lostReasonId: string, notes: string | undefined, tenantId: string, userId: string): Promise<Lead> {
    const lead = await this.findById(id, tenantId);

    if (lead.status === 'converted') {
      throw new ValidationError('No se puede marcar como perdido un lead convertido');
    }

    if (lead.status === 'lost') {
      throw new ValidationError('El lead ya esta marcado como perdido');
    }

    await query(
      `UPDATE crm.leads SET
         status = 'lost',
         lost_reason_id = $1,
         lost_notes = $2,
         date_closed = CURRENT_TIMESTAMP,
         updated_by = $3,
         updated_at = CURRENT_TIMESTAMP
       WHERE id = $4 AND tenant_id = $5`,
      [lostReasonId, notes, userId, id, tenantId]
    );

    return this.findById(id, tenantId);
  }

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

    if (lead.opportunity_id) {
      throw new ConflictError('No se puede eliminar un lead que tiene una oportunidad asociada');
    }

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

export const leadsService = new LeadsService();
