import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from 'src/users/entities/user.entity';
import { SendableUser } from 'src/users/sendableUsers';
import { Repository, Brackets } from 'typeorm';
import { CreateFriendshipDto } from './dto/create-friendship.dto';
import { Friendship, FriendshipStatus } from './entities/friendship.entity';
import { SendableFriendship } from './sendableFriendship';

@Injectable()
export class FriendshipService {

	constructor(
		@InjectRepository(Friendship)
		private readonly friendshipRepository: Repository<Friendship>,
		@InjectRepository(User)
		private readonly userRepository: Repository<User>,
	) { }


	// createSendableFriendship(friendship: Friendship): SendableFriendship {
	// 	let ret = new SendableFriendship;

	// 	ret.id = friendship.id;
	// 	ret.date = friendship.date
	// 	ret.senderUsername = friendship.sender.username;
	// 	ret.senderId = friendship.sender.id;
	// 	ret.receiverUsername = friendship.receiver.username;
	// 	ret.receiverId = friendship.receiver.id;
	// 	ret.status = friendship.status;
	// 	return ret;
	// };

	//kinda useless but someone else might use it
	async findOneRelationshipById(friendId : number, userId : number) {
		const friendship = await this.friendshipRepository
		.createQueryBuilder('friendship')
		.leftJoinAndSelect('friendship.sender', 'sender')
		.leftJoinAndSelect('friendship.receiver', 'receiver')
		.where(
			new Brackets((qb) => {
				qb.where(
					new Brackets((subAQb) => {
						subAQb.where('sender.id = :userId', { userId : userId})
						.andWhere('receiver.id = :friendId', {friendId : friendId})
					})
				)
				.orWhere(
					new Brackets((subBQb) => {
						subBQb.where('sender.id = :friendId2', {friendId2 : friendId})
						.andWhere('receiver.id = :userId2', {userId2 : userId})
					})
				)
			}),
		)
		// .andWhere('friendship.status != :status', {status : FriendshipStatus.BLOCKED})
		.getOne()

		console.log('END Find one friend by ID: ')
		console.log({...friendship})

		if (!friendship) {
			throw new HttpException(`There is no such friendship`, HttpStatus.NOT_FOUND);
		}
		// return friendship;
		// return this.createSendableFriendship(friendship);
		return new SendableFriendship(friendship);
	}

	async findOneRelationshipByUsername(friendUsername : string, username : string) {
		// This seems to work, finding friendships by username but checking the actual users not the friendship
		const friendship = await this.friendshipRepository
		.createQueryBuilder('friendship')
		.leftJoinAndSelect('friendship.sender', 'sender')
		.leftJoinAndSelect('friendship.receiver', 'receiver')
		.where(
			new Brackets((qb) => {
				qb.where(
					new Brackets((subAQb) => {
						subAQb.where('sender.username = :username', {username : username})
						.andWhere('receiver.username = :friendUsername', {friendUsername : friendUsername})
					})
				)
				.orWhere(
					new Brackets((subBQb) => {
						subBQb.where('sender.username = :friendUsername2', {friendUsername2 : friendUsername})
						.andWhere('receiver.username = :username2', {username2 : username})
					})
				)
			}),
		)
		// .andWhere('friendship.status != :status', {status : FriendshipStatus.BLOCKED})
		.getOne()

		console.log('END Find one friend by username: ')
		console.log({...friendship})

		if (!friendship) {
			throw new HttpException(`There is no such friendship`, HttpStatus.NOT_FOUND);
		}
		// return friendship;
		return new SendableFriendship(friendship);
	}

	// maybe i should be returning a list of users not a list of friendships
	// async findAllFriends(userId: number) {
	async findAllFriendships(userId: number) {
		const friendships = await this.friendshipRepository
			.createQueryBuilder('friendship')
			.leftJoinAndSelect('friendship.sender', 'sender')
			.leftJoinAndSelect('friendship.receiver', 'receiver')
			.where('friendship.status = :status', { status: FriendshipStatus.ACCEPTED })
			.andWhere('receiver.id = :addressee', { addressee: userId })
			.orWhere('sender.id = :requester', { requester: userId })
			.andWhere('friendship.status = :status', { status: FriendshipStatus.ACCEPTED })
			.getMany();
		// for (const friend of friendship)
		// 	console.log("FRIENDSHIP : " + friend.status);
		// return friendship;
		let sendFrienships: SendableFriendship[] = [];
		for (const friendship of friendships) {
			sendFrienships.push(new SendableFriendship(friendship));
		}
		// return new SendableFriendship(friendship);
		console.log('friendship.service my friends:')
		console.log({...sendFrienships})
		return sendFrienships;

		// let sendFriends: SendableUser[] = [];
		// for (const friendship of friendships) {
		// 	let user: User;
		// 	if (friendship.sender.id !== userId)
		// 		user = friendship.sender;
		// 	else
		// 		user = friendship.receiver;
		// 	sendFriends.push(new SendableUser(user));
		// }
		// console.log('friendship.service my friends as Users:')
		// console.log({...sendFriends})
		// return sendFriends;

	}

	async findOneBlocked(friendshipId: number, userId: number) {
		// const friendship = await this.friendshipRepository.findOneBy({ id: friendshipId, sender.id: userId, status: FriendshipStatus.BLOCKED });
		const friendship = await this.friendshipRepository
			.createQueryBuilder('friendship')
			.leftJoinAndSelect('friendship.sender', 'sender')
			.leftJoinAndSelect('friendship.receiver', 'receiver')
			.where('friendship.id = :id', { id: friendshipId })
			.andWhere('sender.id = :requester', { requester: userId })
			.andWhere('friendship.status = :status', { status: FriendshipStatus.BLOCKED })
			.getOne();
		if (!friendship)
			throw new HttpException(`The requested blocked not found.`, HttpStatus.NOT_FOUND);
		// return friendship;
		return new SendableFriendship(friendship);
	}


	async findOneBlockedByUsername(blockedUsername : string, userId : number) {
		const friendship = await this.friendshipRepository
		.createQueryBuilder('friendship')
		.leftJoinAndSelect('friendship.sender', 'sender')
		.leftJoinAndSelect('friendship.receiver', 'receiver')
		.where('sender.id = :senderId', {senderId : userId})
		.andWhere('receiver.username = :friendUsername', {friendUsername : blockedUsername})
		.andWhere('friendship.status = :status  ', {status : FriendshipStatus.BLOCKED})
		.getOne()
		if (!friendship) {
			throw new HttpException(`The requested blocked not found.`, HttpStatus.NOT_FOUND);
		}
		return new SendableFriendship(friendship);
	}

	async findAllBlockedFriends(userId: number) {
		const friendships : Friendship[] =  await this.friendshipRepository
			.createQueryBuilder('friendship')
			.leftJoinAndSelect('friendship.sender', 'sender')
			.leftJoinAndSelect('friendship.receiver', 'receiver')
			.where('sender.id = :requestee', { requestee: userId })
			.andWhere('friendship.status = :status', { status: FriendshipStatus.BLOCKED })
			.getMany();
		// let partialFriendship : Partial<Friendship>[] = [];
		// for (const friendship of friendships) {
		// 	partialFriendship.push({id: friendship.id, date: friendship.date, senderUsername: friendship.senderUsername, receiverUsername: friendship.receiverUsername, status: friendship.status});
		// }
		console.log('friendship.service findAllBlockedFriends, friendships:')
		console.log({...friendships})
		console.log('friendship.service findAllBlockedFriends, partial friendship:')
		// console.log({...partialFriendship})
		// return partialFriendship;
		// return friendships;
		let sendFrienships: SendableFriendship[] = []
		for (const friendship of friendships) {
			sendFrienships.push(new SendableFriendship(friendship));
		}
		// return new SendableFriendship(friendship);
		return sendFrienships;
	}

	async findAllPendantRequestsForFriendship(userId: number) {
		const friendships =  await this.friendshipRepository
			.createQueryBuilder('friendship')
			.leftJoinAndSelect('friendship.sender', 'sender')
			.leftJoinAndSelect('friendship.receiver', 'receiver')
			.where('sender.id = :requestee', { requestee: userId })
			.andWhere('friendship.status = :status', { status: FriendshipStatus.REQUESTED })
			.getMany();

		console.log('friendships services pendant friendships:')
		console.log({...friendships})

		let sendFrienships: SendableFriendship[] = []
		for (const friendship of friendships) {
			sendFrienships.push(new SendableFriendship(friendship));
		}
		return sendFrienships;
	}

	async findAllReceivedRequestsForFriendship(userId: number) {
		const friendships = await this.friendshipRepository
			.createQueryBuilder('friendship')
			.leftJoinAndSelect('friendship.sender', 'sender')
			.leftJoinAndSelect('friendship.receiver', 'receiver')
			.where('receiver.id = :addressee', { addressee: userId })
			.andWhere('friendship.status = :status', { status: FriendshipStatus.REQUESTED })
			.getMany();

		console.log('friendship service received requests')
		console.log({...friendships})

		let sendFrienships: SendableFriendship[] = []
		for (const friendship of friendships) {
			sendFrienships.push(new SendableFriendship(friendship));
		}
		return sendFrienships;
	}

	// this is no longer the right return
	// async create(createFriendshipDto: CreateFriendshipDto, creator : User) : Promise <Partial<Friendship>> {
	async create(createFriendshipDto: CreateFriendshipDto, creator : User) {
		console.log("DTO : \n")
		console.log({...createFriendshipDto})

		// These throws don't seem to work...
		const receiver = await this.userRepository.findOneBy({username: createFriendshipDto.receiverUsername});
		// console.log('receiver: ')
		// console.log({...receiver})
		if (!receiver)
			throw new HttpException(`The addressee does not exist.`, HttpStatus.NOT_FOUND);
		if (createFriendshipDto.status !== FriendshipStatus.REQUESTED && createFriendshipDto.status !== FriendshipStatus.BLOCKED)
			throw new HttpException(`The status is not valid.`, HttpStatus.NOT_FOUND);
		
		// const friendship = await this.friendshipRepository.findOneBy({ sender: creator, receiver: receiver });

		const friendship = await this.friendshipRepository
			.createQueryBuilder('friendship')
			.leftJoinAndSelect('friendship.sender', 'sender')
			.leftJoinAndSelect('friendship.receiver', 'receiver')
			.where('sender.id = :senderId', {senderId: creator.id})
			.andWhere('receiver.id = :receiverId', {receiverId: receiver.id})
			.getOne();

		console.log('friendship.service create, friendship: \n\n\n')
		// console.log({...friendship})

		if (friendship) {
			console.log('there is already a friend request')
			if (friendship.status && friendship.status === FriendshipStatus.ACCEPTED)
				throw new HttpException(`The friendship request has already been accepted.`, HttpStatus.OK);
			else if (friendship.status && friendship.status === FriendshipStatus.REQUESTED)
				throw new HttpException(`The friendship request has already been sent the ${friendship.date}.`, HttpStatus.OK);
			else if (friendship.status && friendship.status === FriendshipStatus.BLOCKED)
				throw new HttpException(`We can't do that`, HttpStatus.OK);
			else if (friendship.status && friendship.status === FriendshipStatus.DECLINED)
				throw new HttpException(`The request has been declined.`, HttpStatus.OK);
		}
		console.log('friendship.service create, we are still saving a new friendship')

		const newFriendship = new Friendship();
		newFriendship.sender = creator;
		// newFriendship.senderUsername = creator.username;
		// newFriendship.senderId = creator.id;
		newFriendship.receiver = receiver;
		// newFriendship.receiverUsername = receiver.username;
		// newFriendship.receiverId = receiver.id;
		newFriendship.status = createFriendshipDto.status;
		const savedFriendship = await this.friendshipRepository.save(newFriendship);
		// const partialFriendship : Partial<Friendship> = {
		// 	id : (await savedFriendship).id,
		// 	date : (await savedFriendship).date,
		// 	receiverUsername: (await savedFriendship).receiverUsername,
		// 	receiverId: (await savedFriendship).receiverId,
		// 	status : (await savedFriendship).status
		// }
		// console.log('friendship.service create friendship, partial friendship')
		// console.log({...partialFriendship})
		// console.log('friendship.service create friendship, NEW friendship')
		// console.log({...newFriendship})
		// console.log('friendship.service create friendship, SAVED friendship')
		// console.log({...savedFriendship})
		// return partialFriendship;
		return new SendableFriendship(savedFriendship);
	}

	async acceptFriendship(relationshipId: number, user: User) {
		// const relation = await this.friendshipRepository.findOneBy({id: relationshipId });
		

		const relation = await this.friendshipRepository
			.createQueryBuilder('friendship')
			.leftJoinAndSelect('friendship.sender', 'sender')
			.leftJoinAndSelect('friendship.receiver', 'receiver')
			.where('friendship.id = :friendshipId', { friendshipId: relationshipId })
			.getOne();

		console.log('.service accept friendship')
		console.log({...relation})
		if (!relation)
			throw new HttpException(`The requested relationship not found.`, HttpStatus.NOT_FOUND);
		if (relation.sender.id === user.id) {
			throw new HttpException(`You can't accept your own request.`, HttpStatus.NOT_FOUND);
		}
		relation.status = FriendshipStatus.ACCEPTED;
		const savedFriendship = await this.friendshipRepository.save(relation);
		// const partialFriendship : Partial<Friendship> = {
		// 	id : (await savedFriendship).id,
		// 	date : (await savedFriendship).date,
		// 	receiverUsername: (await savedFriendship).receiverUsername,
		// 	receiverId: (await savedFriendship).receiverId,
		// 	status : (await savedFriendship).status
		// }
		// console.log('.service accept friendship END')
		// console.log({...partialFriendship})

		// return partialFriendship;
		return new SendableFriendship(savedFriendship);
	}

	async declineFriendship(relationshipId: number, user: User) {
		const relation = await this.friendshipRepository
			.createQueryBuilder('friendship')
			.leftJoinAndSelect('friendship.sender', 'sender')
			.leftJoinAndSelect('friendship.receiver', 'receiver')
			.where('friendship.id = :friendshipId', { friendshipId: relationshipId })
			.getOne();
		if (!relation)
			throw new HttpException(`The requested relationship not found.`, HttpStatus.NOT_FOUND);
		if (relation.sender.id === user.id) {
			throw new HttpException(`You can't decline your own request.`, HttpStatus.NOT_FOUND);
		}
		relation.status = FriendshipStatus.DECLINED;
		const savedFriendship = await this.friendshipRepository.save(relation);
		return new SendableFriendship(savedFriendship);
	}

	async blockFriendship(relationshipId: number, user: User) {
		const relation = await this.friendshipRepository
			.createQueryBuilder('friendship')
			.leftJoinAndSelect('friendship.sender', 'sender')
			.leftJoinAndSelect('friendship.receiver', 'receiver')
			.where('friendship.id = :friendshipId', { friendshipId: relationshipId })
			.getOne();
		if (!relation)
			throw new HttpException(`The requested relationship not found.`, HttpStatus.NOT_FOUND);

		// do i need to check if they've already been blocked?

		if (relation.receiver.id === user.id) {
			// throw new HttpException(`You can't block yourself.`, HttpStatus.NOT_FOUND);
			console.log('friendship.service blockFriendship trying to delete and recreate a friendship with block')
			console.log({...relation})
			// const newFriendshipDto = new CreateFriendshipDto(relation[0].receiver, FriendshipStatus.BLOCKED)
			// const newFriendshipDto = new CreateFriendshipDto({"receiverUsername": relation[0].receiver, "status": "R"})
			// we create a new one where you are the sender
			const newFriendshipDto = {"receiverUsername": relation.sender.username, "receiverId": relation.sender.id, "status": FriendshipStatus.BLOCKED};
			// can't do it this way cuz READONLY
			// const newFriendshipDto = new CreateFriendshipDto();
			// newFriendshipDto.receiverUsername = relation[0].sender.username;
			// newFriendshipDto.status = FriendshipStatus.BLOCKED;
			// we delete that relation
			await this.removeFriendship(relationshipId, user);
			// we set it to blocked like we do below...
			return await this.create(newFriendshipDto, user);
		} else {
			relation.status = FriendshipStatus.BLOCKED;
			const savedFriendship = await this.friendshipRepository.save(relation);
			// const partialFriendship : Partial<Friendship> = {
			// 	id : (await savedFriendship).id,
			// 	date : (await savedFriendship).date,
			// 	receiverUsername: (await savedFriendship).receiverUsername,
			// 	receiverId: (await savedFriendship).receiverId,
			// 	status : (await savedFriendship).status
			// }
			// return partialFriendship
			return new SendableFriendship(savedFriendship);
		}
	}

	async removeFriendship(relationshipId: number, user : User) {
		const friendship = await this.friendshipRepository
			.createQueryBuilder('friendship')
			.leftJoinAndSelect('friendship.sender', 'sender')
			.leftJoinAndSelect('friendship.receiver', 'receiver')
			.where('friendship.id = :friendshipId', { friendshipId: relationshipId })
			.getOne();
		console.log('deleting a friendship .service')
		console.log({...friendship})
		console.log('who is the user')
		console.log({...user})
		if (!friendship)
			throw new HttpException(`Your friend could not be deleted.`, HttpStatus.NOT_FOUND);
		if (friendship.sender.id !== user.id && friendship.receiver.id !== user.id) {
				throw new HttpException(`You can't do that.`, HttpStatus.FORBIDDEN);
			}
		console.log('.service deleted a friendship')
		return this.friendshipRepository.remove(friendship);
		// what is the return here? am i getting something right?
	}

	// ok for some reason this isn't working...
	async findIfUserIsBlockedOrHasBlocked(userConnectedId: number, userToFindId: number) {
		console.log("finding if user is blocked")
		console.log('user connected: ' + userConnectedId)
		console.log('user to find: ' + userToFindId)
		
		const friendship = await this.friendshipRepository
			.createQueryBuilder('friendship')
			.leftJoinAndSelect('friendship.sender', 'sender')
			.leftJoinAndSelect('friendship.receiver', 'receiver')
			.where(
				new Brackets((qb) => {
					qb.where(
						new Brackets((subAQb) => {
							subAQb.where('sender.id = :senderId', { senderId: userConnectedId})
							.andWhere('receiver.id = :receiverId', { receiverId: userToFindId})
						})
					)
					.orWhere(
						new Brackets((subBQb) => {
							subBQb.where('sender.id = :senderId2', {senderId2 : userToFindId})
							.andWhere('receiver.id = :receiverId2', {receiverId2 : userConnectedId})
						})
					)
				}),
			)
			.andWhere('friendship.status = :status', {status : FriendshipStatus.BLOCKED})
			.getOne()


		console.log('printing friendship queried')
		console.log({...friendship})
		if (friendship) {
			console.log('we are blocked in friendship.service')
			return true;
		}
		console.log('we are not blocked in friendship service')
		return false;
	}
}
