import 'server-only';
import { db } from '~/server/db';
import * as schema from '~/server/db/schema';
import { eq, and, or, like, not, desc, sql } from 'drizzle-orm';
import type {
  User,
  Relationship,
  UserRelationship,
  RelationshipData,
  Countdown,
  InitialData,
  Message,
  MessageMedia,
  MessageLocation,
  QuickReply,
  QuickReplyOption,
} from '~/server/types';

export const getUser = async (userId: number) => {
  try {
    const users = await db.select().from(schema.users)
      .where(eq(schema.users.id, userId))
    return (users.length > 0) ? users[0] as User : null;
  } catch (error) {
    console.error(error);
    return null;
  }
};

export const getRelationship = async (userId: number) => {
  try {
    const user = await getUser(userId);
    if (!user) throw new Error("User not found");
    const userRelationship = await db.select()
      .from(schema.userRelationships)
      .where(eq(schema.userRelationships.userId, user.id))
      .limit(1)
      .then(results => results[0]);
    if (!userRelationship) throw new Error('No relationships found for user');
    const relationship = await db.select()
      .from(schema.relationships)
      .where(eq(schema.relationships.id, userRelationship.relationshipId))
      .limit(1)
      .then(results => results[0] as Relationship);
    if (!relationship) throw new Error('Relationship not found');
    return relationship;
  } catch (error) {
    console.error('Error getting relationship:', error);
    throw error;
  }
};

export const getInitialDataByAppleId = async (appleId: string) => {
  try {
    const users = await db.select().from(schema.users)
      .where(eq(schema.users.appleId, appleId))
    if (users.length === 0) return null;
    const user = users[0] as User;
    const userRelationships = await db.select()
      .from(schema.userRelationships)
      .where(eq(schema.userRelationships.userId, user.id))

    let relationshipData: RelationshipData | undefined;
    let countdown: Countdown | undefined;

    if (userRelationships.length > 0) {
      const userRelationship = userRelationships[0] as UserRelationship;
      const relationships = await db.select()
        .from(schema.relationships)
        .where(eq(schema.relationships.id, userRelationship.relationshipId))
      if (relationships.length > 0) {
        const relationship = relationships[0] as Relationship;

        const partners = await db.select()
          .from(schema.users)
          .innerJoin(schema.userRelationships,
            eq(schema.users.id, schema.userRelationships.userId))
          .where(
            and(
              eq(schema.userRelationships.relationshipId, relationship.id),
              not(eq(schema.userRelationships.userId, user.id))
            )
          );
        if (partners.length > 0) {
          const partner = partners[0]?.users as User;
          relationshipData = {
            relationship,
            partner,
          };
          const countdowns = await db.select()
            .from(schema.countdowns)
            .where(eq(schema.countdowns.relationshipId, relationship.id))
            .orderBy(schema.countdowns.date)
            .limit(1);
          if (countdowns.length > 0) {
            countdown = countdowns[0] as Countdown;
          }
        }
      }
    }
    const initialData: InitialData = {
      user,
      relationshipData,
      countdown,
    };
    return initialData;
  } catch (error) {
    console.error(error);
    return null;
  }
};

export const createUser = async (
  appleId: string, email: string,
  fullName: string, pushToken: string
) => {
  try {
    console.log(appleId, email, fullName, pushToken);

    if (!appleId || !email || !fullName || !pushToken) {
      throw new Error("Error: All required fields must be filled");
    }
    // Check if username or email is already taken
    const existingUser = await db.select().from(schema.users)
      .where(or(eq(schema.users.appleId, appleId), eq(schema.users.email, email)));

    if (existingUser.length > 0) {
      throw new Error("Username or email is already in use");
    }
    console.log('right before we add the user');

    const newUsers: User[] = await db.insert(schema.users).values({
      appleId, email, fullName, pushToken 
    }).returning() as User[]; // return the newly created user

    const newUser: User | undefined = newUsers[0];

    if (!newUsers.length || !newUsers[0]?.id)
      throw new Error("Failed to create new user");

    return newUser;

  } catch (error) {
    if (error instanceof Error) {
      throw new Error(`Failed to create new user: ${error.message}`);
    } else {
      throw new Error("Unknown error occurred while creating new user");
    }
  }
};

export const updatePushToken = async (userId: number, pushToken: string): Promise<boolean> => {
  try {
    const result = await db.update(schema.users)
      .set({ pushToken: pushToken })
      .where(
        and(
          eq(schema.users.id, userId),
          not(eq(schema.users.pushToken, pushToken))
        )
      )
      .returning({ updatedId: schema.users.id });

    return result.length > 0;
  } catch (error) {
    console.error('Error updating push token:', error);
    return false;
  }
};

export const getPfpUrl = async (userId: number) => {
  try {
    const users = await db.select().from(schema.users)
      .where(eq(schema.users.id, userId))
    console.log(users);
    const user = users[0] as User;
    if (!user) return null;
    return user.pfpUrl;
  } catch (error) {
    console.error('Error getting pfp url:', error);
  }
};

export const updatePfpUrl = async (userId: number, pfpUrl: string) => {
  try {
    const result = await db.update(schema.users)
      .set({ pfpUrl: pfpUrl })
      .where(eq(schema.users.id, userId))
      .returning({ updatedId: schema.users.id });
    return result.length > 0;
  } catch (error) {
    console.error('Error updating pfp url:', error);
  }
};

export const checkRelationshipStatus = async (userId: number): Promise<RelationshipData> => {
  try {
    const user = await getUser(userId);
    if (!user) throw new Error("User not found");
    const userRelationship = await db.select()
      .from(schema.userRelationships)
      .where(eq(schema.userRelationships.userId, user.id))
      .limit(1)
      .then(results => results[0]);
    if (!userRelationship) throw new Error('No relationships found for user');
    const relationship = await db.select()
      .from(schema.relationships)
      .where(eq(schema.relationships.id, userRelationship.relationshipId))
      .limit(1)
      .then(results => results[0] as Relationship);
    if (!relationship) throw new Error('Relationship not found');
    const partner = await db.select()
      .from(schema.users)
      .innerJoin(schema.userRelationships,
        eq(schema.users.id, schema.userRelationships.userId))
      .where(
        and(
          eq(schema.userRelationships.relationshipId, relationship.id),
          not(eq(schema.userRelationships.userId, user.id))
        )
      )
      .limit(1)
      .then(results => results[0]?.users as User);
    if (!partner) throw new Error('No partner found for relationship');
    return { relationship, partner };
  } catch (error) {
    console.error('Error checking relationship status:', error);
    throw error; // Re-throw the error to be handled by the caller
  }
};

export const updateRelationshipStatus = async (
  userId: number, status: 'accepted' | 'rejected'
) => {
  const users = await db.select().from(schema.users)
    .where(eq(schema.users.id, userId));
  const user = users[0] as User;
  if (!user) throw new Error("User not found");
  const userRelationships = await db.select()
    .from(schema.userRelationships)
    .where(eq(schema.userRelationships.userId, user.id));
  if (userRelationships.length === 0) {
    throw new Error('No relationships found for user');
  }
  const userRelationship = userRelationships[0] as UserRelationship;
  const relationships = await db.select()
    .from(schema.relationships)
    .where(eq(schema.relationships.id, userRelationship.relationshipId));
  if (relationships.length === 0) {
    throw new Error('Relationship not found');
  }
  const relationship = relationships[0] as Relationship;
  // Everything above is just getting info
  if (userId === relationship.requestorId && status === 'accepted') {
    throw new Error('The requestor cannot accept the relationship they requested.');
  }
  if (status === 'accepted') {
    await db.update(schema.relationships)
      .set({ isAccepted: true })
      .where(eq(schema.relationships.id, relationship.id));
    const partners = await db.select()
      .from(schema.users)
      .innerJoin(schema.userRelationships,
        eq(schema.users.id, schema.userRelationships.userId))
      .where(
        and(
          eq(schema.userRelationships.relationshipId, relationship.id),
          not(eq(schema.userRelationships.userId, user.id))
        )
      );
    if (partners.length === 0) {
      throw new Error('No partners found for relationship');
    }
    const partner = partners[0]?.users as User;
    const relationshipData: RelationshipData = {
      relationship,
      partner,
    };
    return relationshipData;
  } else if (status === 'rejected') {
    await db.delete(schema.userRelationships)
      .where(eq(schema.userRelationships.relationshipId, relationship.id));
    await db.delete(schema.countdowns)
      .where(eq(schema.countdowns.relationshipId, relationship.id));
    await db.delete(schema.relationships)
      .where(eq(schema.relationships.id, relationship.id));
    return null;
  }
};

export const searchUsers = async (userId: number, searchTerm: string) => {
  try {
    const users = await db.select().from(schema.users)
      .where(
        and(
          or(
            like(schema.users.fullName, `%${searchTerm}%`),
            like(schema.users.email, `%${searchTerm}%`)
          ),
          not(eq(schema.users.id, userId))
        )
      );
      if (users.length === 0) throw new Error("No users found");
      return users as User[];
  } catch (error) {
    console.error('Error searching users:', error);
  }
};

export const createRelationshipRequest = async (userId: number, partnerId: number) => {
  try {
    const user = await getUser(userId);
    if (!user) throw new Error("User not found");
    const partner = await getUser(partnerId);
    if (!partner) throw new Error("Partner not found");
    const existingRelationship = await db.select({
      relationshipId: schema.userRelationships.relationshipId,
      status: schema.relationships.isAccepted,
    })
    .from(schema.userRelationships)
    .innerJoin(
      schema.relationships,
      eq(schema.userRelationships.relationshipId, schema.relationships.id)
    )
    .where(
      or(
        eq(schema.userRelationships.userId, user.id),
        eq(schema.userRelationships.userId, partner.id)
      )
    ).limit(1);
    if (existingRelationship.length > 0) {
      throw new Error("Relationship already exists");
    }
    const newRelationship = await db.insert(schema.relationships).values({
      requestorId: user.id,
    }).returning() as Relationship[];
    if (!newRelationship.length || !newRelationship[0]?.id)
      throw new Error("Failed to create new relationship");
    const relationship = newRelationship[0];
    await db.insert(schema.userRelationships).values([
      { userId: userId, relationshipId: relationship.id },
      { userId: partnerId, relationshipId: relationship.id },
    ]);
    return { relationship, partner };
  } catch (error: unknown) {
    if (error instanceof Error) {
      throw new Error(`Failed to create new relationship: ${error.message}`);
    } else {
      throw new Error("Unknown error occurred while creating new relationship");
    }
  }
};

export const getMessages = async (userId: number, limit: number, offset: number) => {
  try {
    const messages = await db.select().from(schema.messages)
      .where(or(
        eq(schema.messages.senderId, userId),
        eq(schema.messages.receiverId, userId)
      ))
      .limit(limit)
      .offset(offset)
      .orderBy(desc(schema.messages.createdAt), desc(schema.messages.id));
    return messages as Message[];
  } catch (error) {
    console.error('Error getting messages:', error);
    throw error;
  }
};

export const sendMessage = async (message: Message) => {
  try {
    const [newMessage] = await db.insert(schema.messages).values({
      senderId: message.senderId,
      receiverId: message.receiverId,
      text: message.text,
      hasLocation: message.hasLocation,
      hasMedia: message.hasMedia,
      hasQuickReply: message.hasQuickReply,
    }).returning();
    return newMessage as Message;
  } catch (error) {
    console.error('Error sending message:', error);
    throw error;
  }
};

export const setCountdown = async (userId: number, countdown: Countdown) => {
  try {
    const user = await getUser(userId);
    if (!user) throw new Error("User not found");
    const relationship = await getRelationship(userId);
    if (!relationship) throw new Error("Relationship not found");
    const existingCountdown: Countdown | null = await getCountdown(userId);

    // Ensure the date is a valid Date object
    const countdownDate = new Date(countdown.date);
    if (isNaN(countdownDate.getTime())) {
      throw new Error("Invalid date provided");
    }

    let result;
    if (existingCountdown !== null) {
      result = await db.update(schema.countdowns)
        .set({ title: countdown.title, date: countdownDate })
        .where(eq(schema.countdowns.id, existingCountdown.id))
        .returning();
    } else {
      result = await db.insert(schema.countdowns)
        .values({
          relationshipId: relationship.id,
          title: countdown.title,
          date: countdownDate,
        }).returning();
    }
    return result[0] as Countdown;
  } catch (error) {
    console.error('Error setting countdown:', error);
    throw error;
  }
};

export const getCountdown = async (userId: number) => {
  try {
    const user = await getUser(userId);
    if (!user) throw new Error("User not found");
    const relationship = await getRelationship(userId);
    if (!relationship) throw new Error("Relationship not found");
    const countdown = await db.select()
      .from(schema.countdowns)
      .where(eq(schema.countdowns.relationshipId, relationship.id))
      .limit(1)
      .then(results => results[0] as Countdown);
    if (!countdown) {
      console.log('No countdown found');
      return null;
    }
    return countdown;
  } catch (error) {
    console.error('Error getting countdown:', error);
    throw error;
  }
};
