/**
 * Branch Service
 * Servicio para gestión de sucursales
 *
 * @module Branches
 */

import { Repository, FindOptionsWhere, IsNull, Not } from 'typeorm';
import { AppDataSource } from '../../../shared/database/typeorm.config';
import { Branch, BranchType } from '../entities/branch.entity';

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

export interface CreateBranchDto {
  code: string;
  name: string;
  shortName?: string;
  branchType?: BranchType;
  parentId?: string;
  phone?: string;
  email?: string;
  managerId?: string;
  addressLine1?: string;
  addressLine2?: string;
  city?: string;
  state?: string;
  postalCode?: string;
  country?: string;
  latitude?: number;
  longitude?: number;
  geofenceRadius?: number;
  geofenceEnabled?: boolean;
  timezone?: string;
  currency?: string;
  isActive?: boolean;
  isMain?: boolean;
  operatingHours?: Record<string, { open: string; close: string }>;
  settings?: Record<string, any>;
}

export interface UpdateBranchDto {
  name?: string;
  shortName?: string;
  branchType?: BranchType;
  parentId?: string;
  phone?: string;
  email?: string;
  managerId?: string;
  addressLine1?: string;
  addressLine2?: string;
  city?: string;
  state?: string;
  postalCode?: string;
  country?: string;
  latitude?: number;
  longitude?: number;
  geofenceRadius?: number;
  geofenceEnabled?: boolean;
  timezone?: string;
  currency?: string;
  isActive?: boolean;
  isMain?: boolean;
  operatingHours?: Record<string, { open: string; close: string }>;
  settings?: Record<string, any>;
}

export interface BranchFilters {
  branchType?: BranchType;
  isActive?: boolean;
  city?: string;
  state?: string;
  parentId?: string;
  search?: string;
}

export interface PaginationOptions {
  page?: number;
  limit?: number;
}

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

export class BranchService {
  private repository: Repository<Branch>;

  constructor() {
    this.repository = AppDataSource.getRepository(Branch);
  }

  async findAll(
    ctx: ServiceContext,
    filters?: BranchFilters,
    pagination?: PaginationOptions
  ): Promise<PaginatedResult<Branch>> {
    const where: FindOptionsWhere<Branch> = {
      tenantId: ctx.tenantId,
    };

    if (filters?.branchType) {
      where.branchType = filters.branchType;
    }

    if (filters?.isActive !== undefined) {
      where.isActive = filters.isActive;
    }

    if (filters?.city) {
      where.city = filters.city;
    }

    if (filters?.state) {
      where.state = filters.state;
    }

    if (filters?.parentId) {
      where.parentId = filters.parentId;
    }

    const page = pagination?.page || 1;
    const limit = pagination?.limit || 50;
    const skip = (page - 1) * limit;

    const [data, total] = await this.repository.findAndCount({
      where,
      relations: ['parent', 'children'],
      order: { code: 'ASC' },
      skip,
      take: limit,
    });

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

  async findById(ctx: ServiceContext, id: string): Promise<Branch | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId },
      relations: ['parent', 'children', 'userAssignments', 'schedules', 'paymentTerminals'],
    });
  }

  async findByCode(ctx: ServiceContext, code: string): Promise<Branch | null> {
    return this.repository.findOne({
      where: { code, tenantId: ctx.tenantId },
    });
  }

  async findMainBranch(ctx: ServiceContext): Promise<Branch | null> {
    return this.repository.findOne({
      where: { tenantId: ctx.tenantId, isMain: true },
    });
  }

  async findRootBranches(ctx: ServiceContext): Promise<Branch[]> {
    return this.repository.find({
      where: { tenantId: ctx.tenantId, parentId: IsNull() },
      relations: ['children'],
      order: { code: 'ASC' },
    });
  }

  async findChildren(ctx: ServiceContext, parentId: string): Promise<Branch[]> {
    return this.repository.find({
      where: { tenantId: ctx.tenantId, parentId },
      order: { code: 'ASC' },
    });
  }

  async create(ctx: ServiceContext, data: CreateBranchDto): Promise<Branch> {
    const branch = this.repository.create({
      ...data,
      tenantId: ctx.tenantId,
      createdBy: ctx.userId,
    });

    // Build hierarchy path if parent exists
    if (data.parentId) {
      const parent = await this.findById(ctx, data.parentId);
      if (parent) {
        branch.hierarchyPath = parent.hierarchyPath
          ? `${parent.hierarchyPath}/${parent.id}`
          : parent.id;
        branch.hierarchyLevel = parent.hierarchyLevel + 1;
      }
    }

    return this.repository.save(branch);
  }

  async update(ctx: ServiceContext, id: string, data: UpdateBranchDto): Promise<Branch | null> {
    const branch = await this.findById(ctx, id);
    if (!branch) {
      return null;
    }

    // If parent changed, update hierarchy
    if (data.parentId !== undefined && data.parentId !== branch.parentId) {
      if (data.parentId) {
        const newParent = await this.findById(ctx, data.parentId);
        if (newParent) {
          branch.hierarchyPath = newParent.hierarchyPath
            ? `${newParent.hierarchyPath}/${newParent.id}`
            : newParent.id;
          branch.hierarchyLevel = newParent.hierarchyLevel + 1;
        }
      } else {
        branch.hierarchyPath = '';
        branch.hierarchyLevel = 0;
      }
    }

    Object.assign(branch, data);
    if (ctx.userId) {
      branch.updatedBy = ctx.userId;
    }

    return this.repository.save(branch);
  }

  async delete(ctx: ServiceContext, id: string): Promise<boolean> {
    // Check if branch has children
    const children = await this.findChildren(ctx, id);
    if (children.length > 0) {
      throw new Error('Cannot delete branch with children. Delete children first.');
    }

    const result = await this.repository.softDelete({ id, tenantId: ctx.tenantId });
    return result.affected ? result.affected > 0 : false;
  }

  async setAsMain(ctx: ServiceContext, id: string): Promise<Branch | null> {
    // First, unset any current main branch
    await this.repository.update(
      { tenantId: ctx.tenantId, isMain: true },
      { isMain: false }
    );

    // Set new main branch
    return this.update(ctx, id, { isMain: true });
  }

  async getStatistics(ctx: ServiceContext): Promise<{
    total: number;
    active: number;
    inactive: number;
    byType: Record<string, number>;
  }> {
    const branches = await this.repository.find({
      where: { tenantId: ctx.tenantId },
    });

    const byType: Record<string, number> = {};
    branches.forEach(branch => {
      byType[branch.branchType] = (byType[branch.branchType] || 0) + 1;
    });

    return {
      total: branches.length,
      active: branches.filter(b => b.isActive).length,
      inactive: branches.filter(b => !b.isActive).length,
      byType,
    };
  }

  async getHierarchy(ctx: ServiceContext): Promise<Branch[]> {
    const roots = await this.findRootBranches(ctx);

    const loadChildren = async (branch: Branch): Promise<Branch> => {
      const children = await this.findChildren(ctx, branch.id);
      branch.children = await Promise.all(children.map(loadChildren));
      return branch;
    };

    return Promise.all(roots.map(loadChildren));
  }

  async findByLocation(
    ctx: ServiceContext,
    latitude: number,
    longitude: number,
    radiusKm: number = 10
  ): Promise<Branch[]> {
    // Simple distance calculation using Haversine formula approximation
    const latDelta = radiusKm / 111; // 1 degree latitude ~ 111 km
    const lonDelta = radiusKm / (111 * Math.cos(latitude * Math.PI / 180));

    const branches = await this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        isActive: true,
        latitude: Not(IsNull()),
        longitude: Not(IsNull()),
      },
    });

    return branches.filter(branch => {
      if (!branch.latitude || !branch.longitude) return false;
      const latDiff = Math.abs(Number(branch.latitude) - latitude);
      const lonDiff = Math.abs(Number(branch.longitude) - longitude);
      return latDiff <= latDelta && lonDiff <= lonDelta;
    });
  }
}
