import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { createClient } from "@supabase/supabase-js";
import { createConfig } from "../../config.js";
import {
  TEST_AVAILABILITIES,
  TEST_EVENT_TYPES,
  TEST_EVENTS,
  TEST_FEEDBACKS,
  TEST_NOTE_ACCESS,
  TEST_NOTES,
  TEST_SHARED_NOTES,
  TEST_TABLO_ACCESS,
  TEST_TABLO_INVITES,
  TEST_TABLOS,
  TEST_USER_INTRODUCTIONS,
  TEST_USERS,
} from "../fixtures/testData.js";

export interface TestUserData {
  userId: string;
  email: string;
  accessToken: string;
}

export interface TestDatabaseData {
  users: Record<string, TestUserData>;
}

const TEST_DATA_FILE = join(process.cwd(), ".test-data.json");

let testData: TestDatabaseData | null = null;

export function getTestData(): TestDatabaseData {
  if (!testData) {
    // Try to load from file
    if (existsSync(TEST_DATA_FILE)) {
      const fileData = readFileSync(TEST_DATA_FILE, "utf-8");
      testData = JSON.parse(fileData);
    }
  }

  if (!testData) {
    throw new Error("Test database not initialized. Call setupTestDatabase() first.");
  }
  return testData;
}

function saveTestData(data: TestDatabaseData): void {
  testData = data;
  writeFileSync(TEST_DATA_FILE, JSON.stringify(data, null, 2), "utf-8");
}

function clearTestData(): void {
  testData = null;
  if (existsSync(TEST_DATA_FILE)) {
    unlinkSync(TEST_DATA_FILE);
  }
}

export async function setupTestDatabase(): Promise<TestDatabaseData> {
  const config = createConfig();

  // Verify service role key is set
  if (!config.SUPABASE_SERVICE_ROLE_KEY || config.SUPABASE_SERVICE_ROLE_KEY.length < 20) {
    throw new Error(
      "SUPABASE_SERVICE_ROLE_KEY is not properly configured. " +
        "Make sure you have set it in .env.test. " +
        "You can get it by running: supabase status --json | jq -r '.service_role_key'"
    );
  }

  // Create admin client that bypasses RLS
  const adminClient = createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_ROLE_KEY, {
    auth: {
      autoRefreshToken: false,
      persistSession: false,
    },
    db: {
      schema: "public",
    },
  });

  await teardownTestDatabase();

  const users: Record<string, TestUserData> = {};

  try {
    // 1. Create test users in auth.users
    for (const [key, userData] of Object.entries(TEST_USERS)) {
      console.log(`  Creating test user: ${userData.email}`);

      const { data: authData, error: authError } = await adminClient.auth.admin.createUser({
        email: userData.email,
        password: userData.password,
        email_confirm: true,
        user_metadata: {
          first_name: userData.first_name,
          last_name: userData.last_name,
          name: userData.name,
        },
      });

      if (authError || !authData.user) {
        throw new Error(`Failed to create user ${userData.email}: ${authError?.message}`);
      }

      // Create a separate client for sign-in to avoid polluting admin client's auth state
      const userClient = createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_ROLE_KEY, {
        auth: {
          autoRefreshToken: false,
          persistSession: false,
        },
      });

      // Generate access token for the user using signInWithPassword
      const { data: signInData, error: signInError } = await userClient.auth.signInWithPassword({
        email: userData.email,
        password: userData.password,
      });

      if (signInError || !signInData.session) {
        throw new Error(`Failed to sign in ${userData.email}: ${signInError?.message}`);
      }

      users[key] = {
        userId: authData.user.id,
        email: userData.email,
        accessToken: signInData.session.access_token,
      };

      // Update profile with is_temporary flag if needed
      if (userData.is_temporary) {
        const { error: profileError } = await adminClient
          .from("profiles")
          .update({ is_temporary: true })
          .eq("id", authData.user.id);

        if (profileError) {
          console.warn(
            `Warning: Failed to update profile for ${userData.email}: ${profileError.message}`
          );
        }
      }
    }

    const tablosToInsert = TEST_TABLOS.map((tablo) => ({
      id: tablo.id,
      owner_id: users[tablo.owner_key].userId,
      name: tablo.name,
      color: tablo.color,
      // status is now computed from etapes, not set directly
      position: tablo.position,
    }));

    const { error: tablosError } = await adminClient.from("tablos").insert(tablosToInsert);
    if (tablosError) {
      throw new Error(`Failed to create tablos: ${tablosError.message}`);
    }

    // 3. Create tablo_access for collaborators (owners are auto-created by trigger)
    if (TEST_TABLO_ACCESS.length > 0) {
      const accessToInsert = TEST_TABLO_ACCESS.map((access) => ({
        tablo_id: access.tablo_id,
        user_id: users[access.user_key].userId,
        granted_by: users[access.granted_by_key].userId,
        is_active: access.is_active,
        is_admin: access.is_admin,
      }));

      const { error: accessError } = await adminClient.from("tablo_access").insert(accessToInsert);
      if (accessError) {
        throw new Error(`Failed to create tablo access: ${accessError.message}`);
      }
    }

    // 4. Create events
    const eventsToInsert = TEST_EVENTS.map((event) => ({
      id: event.id,
      tablo_id: event.tablo_id,
      title: event.title,
      description: event.description,
      start_date: event.start_date,
      start_time: event.start_time,
      end_time: event.end_time,
      created_by: users[event.created_by_key].userId,
    }));

    const { error: eventsError } = await adminClient.from("events").insert(eventsToInsert);
    if (eventsError) {
      throw new Error(`Failed to create events: ${eventsError.message}`);
    }

    // 5. Create event types
    const eventTypesToInsert = TEST_EVENT_TYPES.map((eventType) => ({
      id: eventType.id,
      user_id: users[eventType.user_key].userId,
      config: eventType.config,
      is_active: eventType.is_active,
    }));

    const { error: eventTypesError } = await adminClient
      .from("event_types")
      .insert(eventTypesToInsert);
    if (eventTypesError) {
      throw new Error(`Failed to create event types: ${eventTypesError.message}`);
    }

    // 6. Create notes
    const notesToInsert = TEST_NOTES.map((note) => ({
      id: note.id,
      user_id: users[note.user_key].userId,
      title: note.title,
      content: note.content,
    }));

    const { error: notesError } = await adminClient.from("notes").insert(notesToInsert);
    if (notesError) {
      throw new Error(`Failed to create notes: ${notesError.message}`);
    }

    // 7. Create note access
    if (TEST_NOTE_ACCESS.length > 0) {
      const noteAccessToInsert = TEST_NOTE_ACCESS.map((access) => ({
        note_id: access.note_id,
        user_id: users[access.user_key].userId,
        tablo_id: access.tablo_id,
        is_active: access.is_active,
      }));

      const { error: noteAccessError } = await adminClient
        .from("note_access")
        .insert(noteAccessToInsert);
      if (noteAccessError) {
        throw new Error(`Failed to create note access: ${noteAccessError.message}`);
      }
    }

    // 8. Create shared notes
    if (TEST_SHARED_NOTES.length > 0) {
      const sharedNotesToInsert = TEST_SHARED_NOTES.map((sharedNote) => ({
        note_id: sharedNote.note_id,
        user_id: users[sharedNote.user_key].userId,
        is_public: sharedNote.is_public,
      }));

      const { error: sharedNotesError } = await adminClient
        .from("shared_notes")
        .insert(sharedNotesToInsert);
      if (sharedNotesError) {
        throw new Error(`Failed to create shared notes: ${sharedNotesError.message}`);
      }
    }

    // 9. Create availabilities
    const availabilitiesToInsert = TEST_AVAILABILITIES.map((availability) => ({
      user_id: users[availability.user_key].userId,
      availability_data: availability.availability_data,
      exceptions: availability.exceptions,
    }));

    const { error: availabilitiesError } = await adminClient
      .from("availabilities")
      .insert(availabilitiesToInsert);
    if (availabilitiesError) {
      throw new Error(`Failed to create availabilities: ${availabilitiesError.message}`);
    }

    // 10. Create user introductions
    if (TEST_USER_INTRODUCTIONS.length > 0) {
      const introductionsToInsert = TEST_USER_INTRODUCTIONS.map((intro) => ({
        user_id: users[intro.user_key].userId,
        config: intro.config,
      }));

      const { error: introductionsError } = await adminClient
        .from("user_introductions")
        .insert(introductionsToInsert);
      if (introductionsError) {
        throw new Error(`Failed to create user introductions: ${introductionsError.message}`);
      }
    }

    // 11. Create feedbacks
    const feedbacksToInsert = TEST_FEEDBACKS.map((feedback) => ({
      user_id: users[feedback.user_key].userId,
      fd_type: feedback.fd_type,
      message: feedback.message,
    }));

    const { error: feedbacksError } = await adminClient.from("feedbacks").insert(feedbacksToInsert);
    if (feedbacksError) {
      throw new Error(`Failed to create feedbacks: ${feedbacksError.message}`);
    }

    // 12. Create tablo invites
    if (TEST_TABLO_INVITES.length > 0) {
      const invitesToInsert = TEST_TABLO_INVITES.map((invite) => ({
        invited_email: invite.invited_email,
        invited_by: users[invite.invited_by_key].userId,
        tablo_id: invite.tablo_id,
        is_pending: invite.is_pending,
        invite_token: `test_token_${invite.invited_email}`,
      }));

      const { error: invitesError } = await adminClient
        .from("tablo_invites")
        .insert(invitesToInsert);
      if (invitesError) {
        throw new Error(`Failed to create tablo invites: ${invitesError.message}`);
      }
    }

    const data = { users };
    saveTestData(data);
    return data;
  } catch (error) {
    console.error("❌ Failed to setup test database:", error);
    throw error;
  }
}

export async function teardownTestDatabase(): Promise<void> {
  const config = createConfig();
  const supabase = createClient(config.SUPABASE_URL, config.SUPABASE_SERVICE_ROLE_KEY, {
    auth: {
      autoRefreshToken: false,
      persistSession: false,
    },
  });

  try {
    // Get all test user IDs from profiles
    const { data: testProfiles } = await supabase
      .from("profiles")
      .select("id, email")
      .like("email", "test_%@%");

    const testUserIds = testProfiles?.map((p) => p.id) || [];

    // Delete in reverse order of creation to respect foreign keys

    // 1. Delete tablo invites
    const { error: invitesError } = await supabase
      .from("tablo_invites")
      .delete()
      .like("invited_email", "test_%");
    if (invitesError) {
      console.warn(`Warning: Failed to delete tablo invites: ${invitesError.message}`);
    }

    // 2. Delete feedbacks
    if (testUserIds.length > 0) {
      const { error: feedbacksError } = await supabase
        .from("feedbacks")
        .delete()
        .in("user_id", testUserIds);
      if (feedbacksError) {
        console.warn(`Warning: Failed to delete feedbacks: ${feedbacksError.message}`);
      }
    }

    // 3. Delete user introductions
    if (testUserIds.length > 0) {
      const { error: introductionsError } = await supabase
        .from("user_introductions")
        .delete()
        .in("user_id", testUserIds);
      if (introductionsError) {
        console.warn(`Warning: Failed to delete user introductions: ${introductionsError.message}`);
      }
    }

    // 4. Delete availabilities
    if (testUserIds.length > 0) {
      const { error: availabilitiesError } = await supabase
        .from("availabilities")
        .delete()
        .in("user_id", testUserIds);
      if (availabilitiesError) {
        console.warn(`Warning: Failed to delete availabilities: ${availabilitiesError.message}`);
      }
    }

    // 5. Delete shared notes
    const { error: sharedNotesError } = await supabase
      .from("shared_notes")
      .delete()
      .like("note_id", "test_%");
    if (sharedNotesError) {
      console.warn(`Warning: Failed to delete shared notes: ${sharedNotesError.message}`);
    }

    // 6. Delete note access
    const { error: noteAccessError } = await supabase
      .from("note_access")
      .delete()
      .like("note_id", "test_%");
    if (noteAccessError) {
      console.warn(`Warning: Failed to delete note access: ${noteAccessError.message}`);
    }

    // 7. Delete notes
    const { error: notesError } = await supabase.from("notes").delete().like("id", "test_%");
    if (notesError) {
      console.warn(`Warning: Failed to delete notes: ${notesError.message}`);
    }

    // 8. Delete event types
    const { error: eventTypesError } = await supabase
      .from("event_types")
      .delete()
      .like("id", "test_%");
    if (eventTypesError) {
      console.warn(`Warning: Failed to delete event types: ${eventTypesError.message}`);
    }

    // 9. Delete events
    const { error: eventsError } = await supabase.from("events").delete().like("id", "test_%");
    if (eventsError) {
      console.warn(`Warning: Failed to delete events: ${eventsError.message}`);
    }

    // 10. Delete tablo access (non-owner records)
    const { error: accessError } = await supabase
      .from("tablo_access")
      .delete()
      .like("tablo_id", "test_%");
    if (accessError) {
      console.warn(`Warning: Failed to delete tablo access: ${accessError.message}`);
    }

    // 11. Delete tablos
    const { error: tablosError } = await supabase.from("tablos").delete().like("id", "test_%");
    if (tablosError) {
      console.warn(`Warning: Failed to delete tablos: ${tablosError.message}`);
    }

    // 12. Delete profiles and auth users
    if (testUserIds.length > 0) {
      console.log(`  Deleting ${testUserIds.length} test users from auth...`);
      for (const userId of testUserIds) {
        const { error: userError } = await supabase.auth.admin.deleteUser(userId);
        if (userError) {
          console.warn(`Warning: Failed to delete user ${userId}: ${userError.message}`);
        }
      }
    }

    clearTestData();
  } catch (error) {
    console.error("❌ Failed to teardown test database:", error);
    // Don't throw in teardown to allow tests to continue
  }
}

export const getTestUser = (key: "owner" | "collab" | "temp"): TestUserData => {
  const testData = getTestData();
  const user = testData.users[key];
  if (!user) {
    throw new Error(`Test user '${key}' not found. Ensure test database is initialized.`);
  }
  return user;
};
