import {
  users,
  profiles,
  preferences,
  likes,
  matches,
  messages,
  reports,
  subscriptions,
  type User,
  type UpsertUser,
  type Profile,
  type InsertProfile,
  type Preference,
  type InsertPreference,
  type Like,
  type InsertLike,
  type Match,
  type InsertMatch,
  type Message,
  type InsertMessage,
  type Report,
  type InsertReport,
  type Subscription,
  type InsertSubscription,
} from "@shared/schema";
import { db } from "./db";
import { eq, and, or, sql, desc, inArray, ne } from "drizzle-orm";

export interface IStorage {
  // Replit Auth user operations
  getUser(id: string): Promise<User | undefined>;
  upsertUser(user: UpsertUser): Promise<User>;
  updateUserProfileData(userId: string, data: Partial<User>): Promise<User>;
  
  // Profile operations
  getProfile(userId: string): Promise<Profile | undefined>;
  createProfile(profile: InsertProfile): Promise<Profile>;
  updateProfile(userId: string, profile: Partial<InsertProfile>): Promise<Profile>;
  
  // Preference operations
  getPreferences(userId: string): Promise<Preference | undefined>;
  upsertPreferences(userId: string, prefs: Partial<InsertPreference>): Promise<Preference>;
  
  // Discovery and matching operations
  getDiscoveryProfiles(userId: string, limit?: number): Promise<any[]>;
  createLike(like: InsertLike): Promise<Like>;
  checkMutualLike(userAId: string, userBId: string): Promise<boolean>;
  createMatch(match: InsertMatch): Promise<Match>;
  getMatches(userId: string): Promise<any[]>;
  
  // Messaging operations
  getMatchMessages(matchId: string): Promise<Message[]>;
  createMessage(message: InsertMessage): Promise<Message>;
  markMessageAsRead(messageId: string): Promise<void>;
  
  // Reporting operations
  createReport(report: InsertReport): Promise<Report>;
  getReports(status?: string): Promise<any[]>;
  
  // Subscription operations
  getSubscription(userId: string): Promise<Subscription | undefined>;
  createOrUpdateSubscription(sub: InsertSubscription): Promise<Subscription>;
}

export class DatabaseStorage implements IStorage {
  async getUser(id: string): Promise<User | undefined> {
    const [user] = await db.select().from(users).where(eq(users.id, id));
    return user;
  }

  async upsertUser(userData: UpsertUser): Promise<User> {
    const [user] = await db
      .insert(users)
      .values(userData)
      .onConflictDoUpdate({
        target: users.id,
        set: {
          ...userData,
          updatedAt: new Date(),
        },
      })
      .returning();
    return user;
  }

  async updateUserProfileData(userId: string, data: Partial<User>): Promise<User> {
    const updateData: any = { ...data, updatedAt: new Date() };
    const [updated] = await db
      .update(users)
      .set(updateData)
      .where(eq(users.id, userId))
      .returning();
    return updated;
  }

  async getProfile(userId: string): Promise<Profile | undefined> {
    const [profile] = await db.select().from(profiles).where(eq(profiles.userId, userId));
    return profile;
  }

  async createProfile(profile: InsertProfile): Promise<Profile> {
    const [newProfile] = await db.insert(profiles).values([profile]).returning();
    return newProfile;
  }

  async updateProfile(userId: string, profile: Partial<InsertProfile>): Promise<Profile> {
    const updateData: any = { ...profile, updatedAt: new Date() };
    const [updated] = await db
      .update(profiles)
      .set(updateData)
      .where(eq(profiles.userId, userId))
      .returning();
    return updated;
  }

  async getPreferences(userId: string): Promise<Preference | undefined> {
    const [prefs] = await db.select().from(preferences).where(eq(preferences.userId, userId));
    return prefs;
  }

  async upsertPreferences(userId: string, prefs: Partial<InsertPreference>): Promise<Preference> {
    const [preference] = await db
      .insert(preferences)
      .values({ userId, ...prefs })
      .onConflictDoUpdate({
        target: preferences.userId,
        set: prefs,
      })
      .returning();
    return preference;
  }

  async getDiscoveryProfiles(userId: string, limit: number = 10): Promise<any[]> {
    // Get user's own preferences and profile
    const [userPrefs] = await db.select().from(preferences).where(eq(preferences.userId, userId));
    const [userProfile] = await db.select().from(users).where(eq(users.id, userId));
    
    if (!userPrefs || !userProfile) {
      return [];
    }

    // Get users already liked/passed
    const likedUsers = await db
      .select({ userId: likes.toUserId })
      .from(likes)
      .where(eq(likes.fromUserId, userId));
    
    const likedUserIds = likedUsers.map(l => l.userId);

    // Find potential matches based on preferences
    const potentialMatches = await db
      .select({
        user: users,
        profile: profiles,
      })
      .from(users)
      .leftJoin(profiles, eq(users.id, profiles.userId))
      .where(
        and(
          ne(users.id, userId),
          likedUserIds.length > 0 ? sql`${users.id} NOT IN (${sql.join(likedUserIds.map(id => sql`${id}`), sql`, `)})` : undefined
        )
      )
      .limit(limit);

    return potentialMatches.filter(m => m.user && m.profile);
  }

  async createLike(like: InsertLike): Promise<Like> {
    const [newLike] = await db.insert(likes).values(like).returning();
    return newLike;
  }

  async checkMutualLike(userAId: string, userBId: string): Promise<boolean> {
    const [mutualLike] = await db
      .select()
      .from(likes)
      .where(
        and(
          eq(likes.fromUserId, userBId),
          eq(likes.toUserId, userAId),
          eq(likes.action, "like")
        )
      );
    
    return !!mutualLike;
  }

  async createMatch(match: InsertMatch): Promise<Match> {
    const [newMatch] = await db.insert(matches).values(match).returning();
    return newMatch;
  }

  async getMatches(userId: string): Promise<any[]> {
    const userMatches = await db
      .select({
        match: matches,
        userA: users,
        userB: users,
        profileA: profiles,
        profileB: profiles,
      })
      .from(matches)
      .leftJoin(users, sql`${users.id} = ${matches.userAId} OR ${users.id} = ${matches.userBId}`)
      .leftJoin(profiles, sql`${profiles.userId} = ${users.id}`)
      .where(
        and(
          or(eq(matches.userAId, userId), eq(matches.userBId, userId)),
          eq(matches.active, true)
        )
      );

    return userMatches;
  }

  async getMatchMessages(matchId: string): Promise<Message[]> {
    const msgs = await db
      .select()
      .from(messages)
      .where(eq(messages.matchId, matchId))
      .orderBy(messages.sentAt);
    
    return msgs;
  }

  async createMessage(message: InsertMessage): Promise<Message> {
    const [newMessage] = await db.insert(messages).values(message).returning();
    return newMessage;
  }

  async markMessageAsRead(messageId: string): Promise<void> {
    await db
      .update(messages)
      .set({ readAt: new Date() })
      .where(eq(messages.id, messageId));
  }

  async createReport(report: InsertReport): Promise<Report> {
    const [newReport] = await db.insert(reports).values(report).returning();
    return newReport;
  }

  async getReports(status?: string): Promise<any[]> {
    const query = db
      .select({
        report: reports,
        reporter: users,
        reportedUser: users,
      })
      .from(reports)
      .leftJoin(users, eq(users.id, reports.reporterId))
      .orderBy(desc(reports.createdAt));
    
    if (status) {
      return await query.where(eq(reports.status, status as any));
    }
    
    return await query;
  }

  async getSubscription(userId: string): Promise<Subscription | undefined> {
    const [sub] = await db
      .select()
      .from(subscriptions)
      .where(eq(subscriptions.userId, userId));
    
    return sub;
  }

  async createOrUpdateSubscription(sub: InsertSubscription): Promise<Subscription> {
    const [subscription] = await db
      .insert(subscriptions)
      .values(sub)
      .onConflictDoUpdate({
        target: subscriptions.userId,
        set: sub,
      })
      .returning();
    
    return subscription;
  }
}

export const storage = new DatabaseStorage();
