import { Request, Response, NextFunction, Router } from 'express';
import { BranchesService } from '../services/branches.service';
import { CreateBranchDto, UpdateBranchDto, AssignUserToBranchDto, CreateBranchScheduleDto } from '../dto';

export class BranchesController {
  public router: Router;

  constructor(private readonly branchesService: BranchesService) {
    this.router = Router();
    this.initializeRoutes();
  }

  private initializeRoutes(): void {
    // Branch CRUD
    this.router.get('/', this.findAll.bind(this));
    this.router.get('/hierarchy', this.getHierarchy.bind(this));
    this.router.get('/main', this.getMainBranch.bind(this));
    this.router.get('/nearby', this.findNearbyBranches.bind(this));
    this.router.get('/:id', this.findOne.bind(this));
    this.router.get('/code/:code', this.findByCode.bind(this));
    this.router.post('/', this.create.bind(this));
    this.router.patch('/:id', this.update.bind(this));
    this.router.delete('/:id', this.delete.bind(this));
    this.router.post('/:id/set-main', this.setAsMainBranch.bind(this));

    // Hierarchy
    this.router.get('/:id/children', this.getChildren.bind(this));
    this.router.get('/:id/parents', this.getParents.bind(this));

    // User Assignments
    this.router.post('/assign', this.assignUser.bind(this));
    this.router.delete('/assign/:userId/:branchId', this.unassignUser.bind(this));
    this.router.get('/user/:userId', this.getUserBranches.bind(this));
    this.router.get('/user/:userId/primary', this.getPrimaryBranch.bind(this));
    this.router.get('/:id/users', this.getBranchUsers.bind(this));

    // Geofencing
    this.router.post('/validate-geofence', this.validateGeofence.bind(this));

    // Schedules
    this.router.get('/:id/schedules', this.getSchedules.bind(this));
    this.router.post('/:id/schedules', this.addSchedule.bind(this));
    this.router.get('/:id/is-open', this.isOpenNow.bind(this));
  }

  // ============================================
  // BRANCH CRUD
  // ============================================

  private async findAll(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const { search, branchType, isActive, parentId, limit, offset } = req.query;

      const result = await this.branchesService.findAll(tenantId, {
        search: search as string,
        branchType: branchType as string,
        isActive: isActive ? isActive === 'true' : undefined,
        parentId: parentId as string,
        limit: limit ? parseInt(limit as string, 10) : undefined,
        offset: offset ? parseInt(offset as string, 10) : undefined,
      });

      res.json(result);
    } catch (error) {
      next(error);
    }
  }

  private async findOne(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const branch = await this.branchesService.findOne(id);

      if (!branch) {
        res.status(404).json({ error: 'Branch not found' });
        return;
      }

      res.json({ data: branch });
    } catch (error) {
      next(error);
    }
  }

  private async findByCode(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const { code } = req.params;
      const branch = await this.branchesService.findByCode(tenantId, code);

      if (!branch) {
        res.status(404).json({ error: 'Branch not found' });
        return;
      }

      res.json({ data: branch });
    } catch (error) {
      next(error);
    }
  }

  private async create(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const userId = req.headers['x-user-id'] as string;
      const dto: CreateBranchDto = req.body;

      const branch = await this.branchesService.create(tenantId, dto, userId);
      res.status(201).json({ data: branch });
    } catch (error) {
      next(error);
    }
  }

  private async update(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const userId = req.headers['x-user-id'] as string;
      const dto: UpdateBranchDto = req.body;

      const branch = await this.branchesService.update(id, dto, userId);

      if (!branch) {
        res.status(404).json({ error: 'Branch not found' });
        return;
      }

      res.json({ data: branch });
    } catch (error) {
      next(error);
    }
  }

  private async delete(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const deleted = await this.branchesService.delete(id);

      if (!deleted) {
        res.status(404).json({ error: 'Branch not found' });
        return;
      }

      res.status(204).send();
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // HIERARCHY
  // ============================================

  private async getHierarchy(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const hierarchy = await this.branchesService.getHierarchy(tenantId);
      res.json({ data: hierarchy });
    } catch (error) {
      next(error);
    }
  }

  private async getChildren(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const { recursive } = req.query;
      const children = await this.branchesService.getChildren(id, recursive === 'true');
      res.json({ data: children });
    } catch (error) {
      next(error);
    }
  }

  private async getParents(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const parents = await this.branchesService.getParents(id);
      res.json({ data: parents });
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // USER ASSIGNMENTS
  // ============================================

  private async assignUser(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const assignedBy = req.headers['x-user-id'] as string;
      const dto: AssignUserToBranchDto = req.body;

      const assignment = await this.branchesService.assignUser(tenantId, dto, assignedBy);
      res.status(201).json({ data: assignment });
    } catch (error) {
      next(error);
    }
  }

  private async unassignUser(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { userId, branchId } = req.params;
      const unassigned = await this.branchesService.unassignUser(userId, branchId);

      if (!unassigned) {
        res.status(404).json({ error: 'Assignment not found' });
        return;
      }

      res.status(204).send();
    } catch (error) {
      next(error);
    }
  }

  private async getUserBranches(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { userId } = req.params;
      const branches = await this.branchesService.getUserBranches(userId);
      res.json({ data: branches });
    } catch (error) {
      next(error);
    }
  }

  private async getPrimaryBranch(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { userId } = req.params;
      const branch = await this.branchesService.getPrimaryBranch(userId);

      if (!branch) {
        res.status(404).json({ error: 'No primary branch found' });
        return;
      }

      res.json({ data: branch });
    } catch (error) {
      next(error);
    }
  }

  private async getBranchUsers(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const users = await this.branchesService.getBranchUsers(id);
      res.json({ data: users });
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // GEOFENCING
  // ============================================

  private async validateGeofence(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { branchId, latitude, longitude } = req.body;

      const result = await this.branchesService.validateGeofence(branchId, latitude, longitude);
      res.json({ data: result });
    } catch (error) {
      next(error);
    }
  }

  private async findNearbyBranches(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const { latitude, longitude, radius } = req.query;

      if (!latitude || !longitude) {
        res.status(400).json({ error: 'Latitude and longitude are required' });
        return;
      }

      const branches = await this.branchesService.findNearbyBranches(
        tenantId,
        parseFloat(latitude as string),
        parseFloat(longitude as string),
        radius ? parseInt(radius as string, 10) : undefined
      );

      res.json({ data: branches });
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // SCHEDULES
  // ============================================

  private async getSchedules(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const schedules = await this.branchesService.getSchedules(id);
      res.json({ data: schedules });
    } catch (error) {
      next(error);
    }
  }

  private async addSchedule(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const dto: CreateBranchScheduleDto = req.body;

      const schedule = await this.branchesService.addSchedule(id, dto);
      res.status(201).json({ data: schedule });
    } catch (error) {
      next(error);
    }
  }

  private async isOpenNow(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const isOpen = await this.branchesService.isOpenNow(id);
      res.json({ data: { isOpen } });
    } catch (error) {
      next(error);
    }
  }

  // ============================================
  // MAIN BRANCH
  // ============================================

  private async getMainBranch(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const tenantId = req.headers['x-tenant-id'] as string;
      const branch = await this.branchesService.getMainBranch(tenantId);

      if (!branch) {
        res.status(404).json({ error: 'No main branch found' });
        return;
      }

      res.json({ data: branch });
    } catch (error) {
      next(error);
    }
  }

  private async setAsMainBranch(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const { id } = req.params;
      const branch = await this.branchesService.setAsMainBranch(id);

      if (!branch) {
        res.status(404).json({ error: 'Branch not found' });
        return;
      }

      res.json({ data: branch });
    } catch (error) {
      next(error);
    }
  }
}
