import { initializeApp, getApp, FirebaseApp } from "firebase/app";
import { 
  getFirestore, 
  collection, 
  query, 
  where, 
  getDocs,
  doc,
  getDoc,
  Firestore
} from "firebase/firestore";
import type { 
  KioskConfig, 
  Content, 
  Template,
  Field,
  Group,
  GroupedTemplateContent,
  TemplateContentStructure,
  TemplateValues
} from "./types";
import { templateSchema, contentSchema, fieldSchema } from "./types";

// Helper function to find field by id
function findFieldById(fields: Field[], fieldId: string): Field | undefined {
  return fields.find(field => field.id === fieldId);
}

// Normalize group names to proper camelCase
function normalizeGroupName(name: string): string {
  let words = name.split(/[\s\-_]+/);
  if (words.length === 1) {
    words = words[0]
      .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
      .replace(/([a-zA-Z])(\d)/g, '$1 $2')
      .replace(/([a-z])([a-z]*)/gi, (match, first, rest) => `${first}${rest.toLowerCase()}`)
      .split(/\s+/);
  }

  words = words
    .filter(word => word.length > 0)
    .map(word => word.toLowerCase());

  if (words.length === 0) return '';

  return words[0] + words.slice(1)
    .map(word => word.charAt(0).toUpperCase() + word.slice(1))
    .join('');
}

function processTemplateValues(
  template: Template,
  rawData: Record<string, any> | undefined,
  content: Content
): TemplateValues {
  console.log("Raw content being processed:", rawData);
  console.log("Template being used:", {
    id: template.id,
    name: template.name,
    fields: template.fields,
    groups: template.groups
  });

  const result: TemplateValues = {
    groups: {},
    ungrouped: {}
  };

  if (!rawData) {
    console.log('No raw data provided');
    return result;
  }

  // First, try to get values from templateValues if they exist
  let existingValues = rawData.templateValues || {};
  console.log("Existing template values:", existingValues);

  // Process fields in groups
  Object.entries(template.groups || {}).forEach(([groupId, group]) => {
    // Always generate and use the proper camelCase name
    const normalizedName = normalizeGroupName(group.name);
    console.log(`Processing group ${group.name} -> ${normalizedName}`);

    // Store the normalizedName back to the group for consistency
    group.normalizedName = normalizedName;

    // Initialize group
    result.groups[normalizedName] = {};

    // Check multiple locations for values in order of precedence
    const groupValues = existingValues.groups?.[normalizedName] || 
                      existingValues.groups?.[group.name] ||
                      rawData[normalizedName] || 
                      rawData[group.name] || 
                      {};

    console.log(`Group values found for ${normalizedName}:`, groupValues);

    // Process fields in this group
    group.fieldIds.forEach(fieldId => {
      const field = findFieldById(template.fields, fieldId);
      if (field) {
        const normalizedFieldName = field.name.charAt(0).toLowerCase() + field.name.slice(1);

        // Try to get value from various locations
        const fieldValue = groupValues[normalizedFieldName] || 
                        groupValues[field.name] ||
                        rawData[normalizedFieldName] ||
                        rawData[field.name] ||
                        field.defaultValue || 
                        null;

        console.log(`Field ${field.name} -> ${normalizedFieldName} in group ${normalizedName}:`, {
          value: fieldValue,
          source: groupValues[normalizedFieldName] ? 'normalized group' :
                groupValues[field.name] ? 'original group' :
                rawData[normalizedFieldName] ? 'normalized root' :
                rawData[field.name] ? 'original root' :
                field.defaultValue ? 'default' : 'null'
        });

        result.groups[normalizedName][normalizedFieldName] = fieldValue;
      }
    });
  });

  // Process ungrouped fields
  const groupedFieldIds = new Set(
    Object.values(template.groups || {})
      .flatMap(group => group.fieldIds)
  );

  template.fields.forEach(field => {
    if (!groupedFieldIds.has(field.id)) {
      const normalizedFieldName = field.name.charAt(0).toLowerCase() + field.name.slice(1);
      result.ungrouped[normalizedFieldName] = rawData[normalizedFieldName] || 
                                          rawData[field.name] ||
                                          existingValues.ungrouped?.[normalizedFieldName] || 
                                          existingValues.ungrouped?.[field.name] || 
                                          field.defaultValue || 
                                          null;
    }
  });

  console.log("Final processed template values:", result);
  return result;
}

export class KioskClient {
  private app: FirebaseApp;
  private db: Firestore;

  constructor(config: KioskConfig) {
    try {
      this.app = getApp();
    } catch {
      this.app = initializeApp({
        apiKey: config.apiKey,
        authDomain: config.authDomain || `${config.projectId}.firebaseapp.com`,
        projectId: config.projectId,
      });
    }
    this.db = getFirestore(this.app);
  }

  async getTemplates(): Promise<Template[]> {
    const templatesRef = collection(this.db, "templates");
    const snapshot = await getDocs(templatesRef);
    const templates = snapshot.docs.map(doc => {
      const data = doc.data();

      // Ensure fields is an array
      if (!Array.isArray(data.fields)) {
        data.fields = Object.values(data.fields);
      }

      // Ensure all groups have normalizedName in camelCase
      if (data.groups) {
        Object.values(data.groups).forEach((group: any) => {
          group.normalizedName = normalizeGroupName(group.name);
        });
      }

      return { id: doc.id, ...data } as Template;
    });
    return templates;
  }

  async getTemplateById(templateId: string): Promise<Template | null> {
    const docRef = doc(this.db, "templates", templateId);
    const docSnap = await getDoc(docRef);

    if (!docSnap.exists()) {
      console.log(`Template ${templateId} not found`);
      return null;
    }

    const data = docSnap.data();

    // Ensure fields is an array
    if (!Array.isArray(data.fields)) {
      data.fields = Object.values(data.fields);
    }

    // Ensure all groups have normalizedName in camelCase
    if (data.groups) {
      Object.values(data.groups).forEach((group: any) => {
        group.normalizedName = normalizeGroupName(group.name);
      });
    }

    const template = { id: docSnap.id, ...data } as Template;
    console.log(`Retrieved template ${templateId}:`, template);
    return template;
  }

  async getPublishedContent(): Promise<Content[]> {
    const templates = await this.getTemplates();
    console.log(`Retrieved ${templates.length} templates`);

    const contentRef = collection(this.db, "contents");
    const publishedQuery = query(contentRef, where("published", "==", true));
    const snapshot = await getDocs(publishedQuery);
    console.log(`Found ${snapshot.size} published content items`);

    return Promise.all(snapshot.docs.map(async doc => {
      const rawData = doc.data();
      console.log(`Processing content ${doc.id}:`, {
        templateId: rawData.templateId,
        rawValues: rawData
      });

      console.log("Raw content data from Firestore:", rawData);

      const content = {
        id: doc.id,
        ...rawData,
        templateValues: {
          groups: {},
          ungrouped: {}
        }
      } as Content;

      if (content.templateId) {
        const template = templates.find(t => t.id === content.templateId);
        if (template) {
          const templateValues = processTemplateValues(template, rawData, content);
          console.log("Final processed template values:", templateValues);
          content.templateValues = templateValues;
          console.log(`Processed content ${doc.id} template values:`, content.templateValues);
        }
      }

      return content;
    }));
  }

  async getContentByProject(projectId: string): Promise<Content[]> {
    const templates = await this.getTemplates();
    const contentRef = collection(this.db, "contents");
    const projectQuery = query(
      contentRef, 
      where("published", "==", true),
      where("projectIds", "array-contains", projectId)
    );

    const snapshot = await getDocs(projectQuery);
    return Promise.all(
      snapshot.docs.map(async (doc) => {
        const rawData = doc.data();
        const content = {
          id: doc.id,
          ...rawData,
          templateValues: {
            groups: {},
            ungrouped: {}
          }
        } as Content;

        if (content.templateId) {
          const template = templates.find(t => t.id === content.templateId);
          if (template) {
            content.templateValues = processTemplateValues(template, rawData, content);
          }
        }

        return content;
      })
    );
  }

  async getContentWithTemplate(contentId: string): Promise<{ 
    content: Content; 
    template: Template | null;
    groupedContent?: Promise<TemplateContentStructure>;
  }> {
    const docRef = doc(this.db, "contents", contentId);
    const docSnap = await getDoc(docRef);

    if (!docSnap.exists()) {
      throw new Error("Content not found");
    }

    const rawData = docSnap.data();
    const content = {
      id: docSnap.id,
      ...rawData,
      templateValues: {
        groups: {},
        ungrouped: {}
      }
    } as Content;

    if (!content.templateId) {
      return { content, template: null };
    }

    const template = await this.getTemplateById(content.templateId);
    if (!template) {
      return { content, template: null };
    }

    content.templateValues = processTemplateValues(template, rawData, content);

    return { 
      content, 
      template,
      groupedContent: this.getGroupedTemplateContent(template, content)
    };
  }

  async getGroupedTemplateContent(
    template: Template,
    content: Content
  ): Promise<TemplateContentStructure> {
    const templateValues = content.templateValues ?? { groups: {}, ungrouped: {} };

    const groups = (template.groupOrder || [])
      .map(groupId => {
        const group = template.groups[groupId];
        if (!group) return null;

        const normalizedName = normalizeGroupName(group.name);
        const fields = group.fieldIds
          .map(fieldId => findFieldById(template.fields, fieldId))
          .filter((field): field is Field => field !== undefined);

        return {
          group: {
            ...group,
            normalizedName,
          },
          fields,
          values: templateValues.groups[normalizedName] ?? {}
        };
      })
      .filter((group): group is GroupedTemplateContent => group !== null);

    const ungroupedFieldIds = template.fields
      .filter(field => !Object.values(template.groups)
        .some(group => group.fieldIds?.includes(field.id)))
      .map(field => field);

    return {
      groups,
      ungroupedFields: {
        fields: ungroupedFieldIds,
        values: templateValues.ungrouped ?? {}
      }
    };
  }
}

export function isValidTemplate(template: unknown): template is Template {
  const result = templateSchema.safeParse(template);
  return result.success;
}

export function isValidContent(content: unknown): content is Content {
  const result = contentSchema.safeParse(content);
  return result.success;
}

export function validateTemplateField(field: unknown): field is Field {
  const result = fieldSchema.safeParse(field);
  return result.success;
}