/**
 * PersonService - Gestión de Personas
 *
 * CRUD de personas/contactos con verificación de identidad.
 * Soporta multi-tenant.
 *
 * @module Profiles
 */

import { Repository, DataSource, IsNull } from 'typeorm';
import { Person } from '../entities/person.entity';

interface ServiceContext {
  tenantId: string;
  userId?: string;
}

interface PaginatedResult<T> {
  data: T[];
  total: number;
  page: number;
  limit: number;
  totalPages: number;
}

export interface CreatePersonDto {
  fullName: string;
  firstName?: string;
  lastName?: string;
  maternalName?: string;
  email: string;
  phone?: string;
  mobilePhone?: string;
  identificationType?: string;
  identificationNumber?: string;
  identificationExpiry?: Date;
  address?: Record<string, any>;
  isResponsibleForTenant?: boolean;
}

export interface UpdatePersonDto {
  fullName?: string;
  firstName?: string;
  lastName?: string;
  maternalName?: string;
  email?: string;
  phone?: string;
  mobilePhone?: string;
  identificationType?: string;
  identificationNumber?: string;
  identificationExpiry?: Date;
  address?: Record<string, any>;
  isResponsibleForTenant?: boolean;
}

export interface PersonFilters {
  search?: string;
  isVerified?: boolean;
  isResponsibleForTenant?: boolean;
  page?: number;
  limit?: number;
}

export interface VerifyPersonDto {
  verifiedBy: string;
}

export class PersonService {
  private personRepo: Repository<Person>;

  constructor(dataSource: DataSource) {
    this.personRepo = dataSource.getRepository(Person);
  }

  /**
   * Create a new person
   */
  async create(_ctx: ServiceContext, dto: CreatePersonDto): Promise<Person> {
    // Check for duplicate email
    const existing = await this.findByEmail(dto.email);
    if (existing) {
      throw new Error(`Person with email ${dto.email} already exists`);
    }

    const person = this.personRepo.create({
      ...dto,
      isVerified: false,
    });

    return this.personRepo.save(person);
  }

  /**
   * Find person by ID
   */
  async findById(id: string): Promise<Person | null> {
    return this.personRepo.findOne({
      where: {
        id,
        deletedAt: IsNull(),
      },
    });
  }

  /**
   * Find person by email
   */
  async findByEmail(email: string): Promise<Person | null> {
    return this.personRepo.findOne({
      where: {
        email,
        deletedAt: IsNull(),
      },
    });
  }

  /**
   * Find all persons with filters
   */
  async findAll(_ctx: ServiceContext, filters: PersonFilters = {}): Promise<PaginatedResult<Person>> {
    const page = filters.page || 1;
    const limit = Math.min(filters.limit || 20, 100);
    const skip = (page - 1) * limit;

    const qb = this.personRepo
      .createQueryBuilder('p')
      .where('p.deleted_at IS NULL');

    if (filters.search) {
      qb.andWhere(
        '(p.full_name ILIKE :search OR p.email ILIKE :search OR p.phone ILIKE :search OR p.mobile_phone ILIKE :search)',
        { search: `%${filters.search}%` }
      );
    }

    if (filters.isVerified !== undefined) {
      qb.andWhere('p.is_verified = :isVerified', { isVerified: filters.isVerified });
    }

    if (filters.isResponsibleForTenant !== undefined) {
      qb.andWhere('p.is_responsible_for_tenant = :isResponsible', {
        isResponsible: filters.isResponsibleForTenant
      });
    }

    qb.orderBy('p.full_name', 'ASC').skip(skip).take(limit);

    const [data, total] = await qb.getManyAndCount();

    return {
      data,
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  /**
   * Update person
   */
  async update(id: string, dto: UpdatePersonDto): Promise<Person | null> {
    const person = await this.findById(id);
    if (!person) {
      return null;
    }

    // Check email uniqueness if updating
    if (dto.email && dto.email !== person.email) {
      const existing = await this.findByEmail(dto.email);
      if (existing) {
        throw new Error(`Email ${dto.email} is already in use`);
      }
    }

    Object.assign(person, dto);
    return this.personRepo.save(person);
  }

  /**
   * Soft delete person
   */
  async delete(id: string): Promise<boolean> {
    const person = await this.findById(id);
    if (!person) {
      return false;
    }

    await this.personRepo.update(id, { deletedAt: new Date() });
    return true;
  }

  /**
   * Verify person identity
   */
  async verify(ctx: ServiceContext, id: string): Promise<Person | null> {
    const person = await this.findById(id);
    if (!person) {
      return null;
    }

    if (person.isVerified) {
      throw new Error('Person is already verified');
    }

    // Check required identification fields
    if (!person.identificationType || !person.identificationNumber) {
      throw new Error('Identification type and number are required for verification');
    }

    person.isVerified = true;
    person.verifiedAt = new Date();
    person.verifiedBy = ctx.userId || '';

    return this.personRepo.save(person);
  }

  /**
   * Revoke verification
   */
  async revokeVerification(id: string): Promise<Person | null> {
    const person = await this.findById(id);
    if (!person) {
      return null;
    }

    person.isVerified = false;
    person.verifiedAt = null as any;
    person.verifiedBy = null as any;

    return this.personRepo.save(person);
  }

  /**
   * Find by identification number
   */
  async findByIdentification(type: string, number: string): Promise<Person | null> {
    return this.personRepo.findOne({
      where: {
        identificationType: type,
        identificationNumber: number,
        deletedAt: IsNull(),
      },
    });
  }

  /**
   * Get persons with expiring identification
   */
  async getExpiringIdentifications(daysAhead: number = 30): Promise<Person[]> {
    const expiryDate = new Date();
    expiryDate.setDate(expiryDate.getDate() + daysAhead);

    return this.personRepo
      .createQueryBuilder('p')
      .where('p.deleted_at IS NULL')
      .andWhere('p.identification_expiry IS NOT NULL')
      .andWhere('p.identification_expiry <= :expiryDate', { expiryDate })
      .andWhere('p.identification_expiry > :today', { today: new Date() })
      .orderBy('p.identification_expiry', 'ASC')
      .getMany();
  }

  /**
   * Get tenant responsibles
   */
  async getTenantResponsibles(): Promise<Person[]> {
    return this.personRepo.find({
      where: {
        isResponsibleForTenant: true,
        deletedAt: IsNull(),
      },
      order: { fullName: 'ASC' },
    });
  }

  /**
   * Set as tenant responsible
   */
  async setAsTenantResponsible(id: string, isResponsible: boolean): Promise<Person | null> {
    const person = await this.findById(id);
    if (!person) {
      return null;
    }

    person.isResponsibleForTenant = isResponsible;
    return this.personRepo.save(person);
  }

  /**
   * Get statistics
   */
  async getStats(): Promise<PersonStats> {
    const qb = this.personRepo.createQueryBuilder('p').where('p.deleted_at IS NULL');

    const total = await qb.getCount();
    const verified = await qb.clone().andWhere('p.is_verified = true').getCount();
    const unverified = total - verified;
    const responsibles = await qb.clone().andWhere('p.is_responsible_for_tenant = true').getCount();

    // Get expiring identifications count (next 30 days)
    const expiryDate = new Date();
    expiryDate.setDate(expiryDate.getDate() + 30);
    const expiringIds = await qb
      .clone()
      .andWhere('p.identification_expiry IS NOT NULL')
      .andWhere('p.identification_expiry <= :expiryDate', { expiryDate })
      .andWhere('p.identification_expiry > :today', { today: new Date() })
      .getCount();

    return {
      total,
      verified,
      unverified,
      tenantResponsibles: responsibles,
      expiringIdentifications: expiringIds,
    };
  }
}

export interface PersonStats {
  total: number;
  verified: number;
  unverified: number;
  tenantResponsibles: number;
  expiringIdentifications: number;
}
