import mongoose from 'mongoose';
import { v4 as uuidv4 } from 'uuid';
import type { HouseholdsRepository } from './households.repository.js';
import type { UsersRepository } from '../users/users.repository.js';
import type { CreateHouseholdInput, UpdateHouseholdInput } from '@meshitrack/shared';
import { HouseholdRole } from '@meshitrack/shared';
import { NotFoundError, ForbiddenError, ConflictError } from '../../common/errors.js';

interface Deps {
  householdsRepository: HouseholdsRepository;
  usersRepository: UsersRepository;
}

export class HouseholdsService {
  private readonly householdsRepository: HouseholdsRepository;
  private readonly usersRepository: UsersRepository;

  public constructor({ householdsRepository, usersRepository }: Deps) {
    this.householdsRepository = householdsRepository;
    this.usersRepository = usersRepository;
  }

  public async create(data: CreateHouseholdInput, ownerKeycloakId: string) {
    const inviteCode = uuidv4().slice(0, 8).toUpperCase();
    const session = await mongoose.startSession();
    try {
      session.startTransaction();

      const household = await this.householdsRepository.create(
        data,
        ownerKeycloakId,
        inviteCode,
        session,
      );

      const user = await this.usersRepository.findByKeycloakId(ownerKeycloakId, session);
      if (user) {
        const householdId = household._id.toString();
        await this.usersRepository.update(
          ownerKeycloakId,
          {
            householdIds: [...user.householdIds, householdId],
            defaultHouseholdId: user.defaultHouseholdId ?? householdId,
          },
          session,
        );
      }

      await session.commitTransaction();
      return household;
    } catch (err) {
      await session.abortTransaction();
      throw err;
    } finally {
      session.endSession();
    }
  }

  public async getById(id: string) {
    const household = await this.householdsRepository.findById(id);
    if (!household) {
      throw new NotFoundError('Household not found');
    }
    return household;
  }

  public async update(id: string, data: UpdateHouseholdInput, requestingUserId: string) {
    const household = await this.getById(id);
    const member = household.members.find((m) => m.userId === requestingUserId);
    if (!member || (member.role !== HouseholdRole.OWNER && member.role !== HouseholdRole.ADMIN)) {
      throw new ForbiddenError('Only owners and admins can update household settings');
    }
    const updated = await this.householdsRepository.update(id, data);
    if (!updated) throw new NotFoundError('Household not found');
    return updated;
  }

  public async generateInviteCode(id: string, requestingUserId: string) {
    const household = await this.getById(id);
    const member = household.members.find((m) => m.userId === requestingUserId);
    if (!member || (member.role !== HouseholdRole.OWNER && member.role !== HouseholdRole.ADMIN)) {
      throw new ForbiddenError('Only owners and admins can generate invite codes');
    }
    const newCode = uuidv4().slice(0, 8).toUpperCase();
    const updated = await this.householdsRepository.updateInviteCode(id, newCode);
    if (!updated) throw new NotFoundError('Household not found');
    return updated;
  }

  public async join(inviteCode: string, userId: string) {
    const household = await this.householdsRepository.findByInviteCode(inviteCode);
    if (!household) {
      throw new NotFoundError('Invalid invite code');
    }

    const alreadyMember = household.members.some((m) => m.userId === userId);
    if (alreadyMember) {
      throw new ConflictError('Already a member of this household');
    }

    const session = await mongoose.startSession();
    try {
      session.startTransaction();

      const updated = await this.householdsRepository.addMember(
        household._id.toString(),
        userId,
        HouseholdRole.MEMBER,
        session,
      );
      if (!updated) throw new NotFoundError('Household not found');

      const user = await this.usersRepository.findByKeycloakId(userId, session);
      if (user) {
        const householdId = household._id.toString();
        await this.usersRepository.update(
          userId,
          {
            householdIds: [...user.householdIds, householdId],
            defaultHouseholdId: user.defaultHouseholdId ?? householdId,
          },
          session,
        );
      }

      await session.commitTransaction();
      return updated;
    } catch (err) {
      await session.abortTransaction();
      throw err;
    } finally {
      session.endSession();
    }
  }
}
