/**
 * Survey repository — Cosmos DB CRUD + response aggregation
 * @module surveys/repository
 */

import { getContainer } from '../../lib/cosmos.js';
import {
  Survey,
  SurveyResponse,
  UserSurveyState,
  QuestionAnswer,
  type SurveyStatus,
} from './types.js';

// =============================================================================
// Survey CRUD
// =============================================================================

export async function createSurvey(doc: Survey): Promise<Survey> {
  const container = getContainer('surveys');
  const { resource } = await container.items.create(doc);
  if (!resource) throw new Error('Failed to create survey');
  return resource as unknown as Survey;
}

export async function getSurvey(id: string, productId: string): Promise<Survey | null> {
  const container = getContainer('surveys');
  try {
    const { resource } = await container.item(id, productId).read();
    return resource as unknown as Survey | null;
  } catch (err) {
    if ((err as { code?: number }).code === 404) return null;
    throw err;
  }
}

export async function listSurveys(
  productId: string,
  options?: { status?: SurveyStatus; limit?: number; offset?: number }
): Promise<{ surveys: Survey[]; total: number }> {
  const container = getContainer('surveys');

  let query = 'SELECT * FROM c WHERE c.productId = @productId';
  const parameters = [{ name: '@productId', value: productId }];

  if (options?.status) {
    query += ' AND c.status = @status';
    parameters.push({ name: '@status', value: options.status });
  }

  query += ' ORDER BY c.createdAt DESC';

  const countQuery = query.replace('SELECT *', 'SELECT VALUE COUNT(1)');
  const { resources: countResult } = await container.items
    .query<number>({ query: countQuery, parameters })
    .fetchAll();
  const total = countResult[0] ?? 0;

  if (options?.limit) {
    query += ` OFFSET ${options.offset ?? 0} LIMIT ${options.limit}`;
  }

  const { resources } = await container.items.query<Survey>({ query, parameters }).fetchAll();
  return { surveys: resources, total };
}

export async function updateSurvey(
  id: string,
  productId: string,
  updates: Partial<Survey>
): Promise<Survey | null> {
  const container = getContainer('surveys');

  const existing = await getSurvey(id, productId);
  if (!existing) return null;

  const updated: Survey = {
    ...existing,
    ...updates,
    id: existing.id,
    productId: existing.productId,
    updatedAt: new Date().toISOString(),
  };

  const { resource } = await container.items.upsert(updated);
  if (!resource) throw new Error('Failed to update survey');
  return resource as unknown as Survey;
}

export async function deleteSurvey(id: string, productId: string): Promise<boolean> {
  const container = getContainer('surveys');
  try {
    await container.item(id, productId).delete();
    return true;
  } catch (err) {
    if ((err as { code?: number }).code === 404) return false;
    throw err;
  }
}

// =============================================================================
// Survey Responses
// =============================================================================

export async function createResponse(doc: SurveyResponse): Promise<SurveyResponse> {
  const container = getContainer('survey_responses');
  const { resource } = await container.items.create(doc);
  if (!resource) throw new Error('Failed to create response');
  return resource as unknown as SurveyResponse;
}

export async function getResponse(
  surveyId: string,
  userId: string
): Promise<SurveyResponse | null> {
  const container = getContainer('survey_responses');
  const id = `${surveyId}:${userId}`;
  try {
    const { resource } = await container.item(id, surveyId).read();
    return resource as unknown as SurveyResponse | null;
  } catch (err) {
    if ((err as { code?: number }).code === 404) return null;
    throw err;
  }
}

export async function updateResponse(
  surveyId: string,
  userId: string,
  updates: Partial<SurveyResponse>
): Promise<SurveyResponse | null> {
  const container = getContainer('survey_responses');
  const id = `${surveyId}:${userId}`;

  try {
    const { resource: existing } = await container.item(id, surveyId).read();
    if (!existing) return null;

    const updated: SurveyResponse = {
      ...(existing as SurveyResponse),
      ...updates,
      id,
      surveyId,
      userId,
      updatedAt: new Date().toISOString(),
    };

    const { resource } = await container.items.upsert(updated);
    if (!resource) throw new Error('Failed to update response');
    return resource as unknown as SurveyResponse;
  } catch (err) {
    if ((err as { code?: number }).code === 404) return null;
    throw err;
  }
}

export async function listResponsesForSurvey(
  surveyId: string,
  options?: { isComplete?: boolean; limit?: number; offset?: number }
): Promise<{ responses: SurveyResponse[]; total: number }> {
  const container = getContainer('survey_responses');

  let query = 'SELECT * FROM c WHERE c.surveyId = @surveyId';
  const parameters = [{ name: '@surveyId', value: surveyId }];

  if (options?.isComplete !== undefined) {
    query += ' AND c.isComplete = @isComplete';
    parameters.push({ name: '@isComplete', value: options.isComplete as unknown as string });
  }

  query += ' ORDER BY c.createdAt DESC';

  const countQuery = query.replace('SELECT *', 'SELECT VALUE COUNT(1)');
  const { resources: countResult } = await container.items
    .query<number>({ query: countQuery, parameters })
    .fetchAll();
  const total = countResult[0] ?? 0;

  if (options?.limit) {
    query += ` OFFSET ${options.offset ?? 0} LIMIT ${options.limit}`;
  }

  const { resources } = await container.items
    .query<SurveyResponse>({ query, parameters })
    .fetchAll();
  return { responses: resources, total };
}

export async function listRespondents(surveyId: string): Promise<string[]> {
  const container = getContainer('survey_responses');

  const query = 'SELECT c.userId FROM c WHERE c.surveyId = @surveyId AND c.isComplete = true';
  const parameters = [{ name: '@surveyId', value: surveyId }];

  const { resources } = await container.items
    .query<{ userId: string }>({ query, parameters })
    .fetchAll();
  return resources.map(r => r.userId);
}

// =============================================================================
// User Survey State (per-user tracking)
// =============================================================================

export async function getUserSurveyState(
  surveyId: string,
  userId: string
): Promise<UserSurveyState | null> {
  const container = getContainer('user_survey_states');
  const id = `${surveyId}:${userId}`;
  try {
    const { resource } = await container.item(id, userId).read();
    return resource as unknown as UserSurveyState | null;
  } catch (err) {
    if ((err as { code?: number }).code === 404) return null;
    throw err;
  }
}

export async function upsertUserSurveyState(
  state: Omit<UserSurveyState, 'createdAt' | 'updatedAt'> & { createdAt?: string }
): Promise<UserSurveyState> {
  const container = getContainer('user_survey_states');
  const now = new Date().toISOString();

  const doc: UserSurveyState = {
    ...state,
    createdAt: state.createdAt ?? now,
    updatedAt: now,
  };

  const { resource } = await container.items.upsert(doc);
  if (!resource) throw new Error('Failed to upsert user survey state');
  return resource as unknown as UserSurveyState;
}

export async function incrementSurveyShowCount(
  surveyId: string,
  userId: string,
  productId: string
): Promise<void> {
  const existing = await getUserSurveyState(surveyId, userId);
  const now = new Date().toISOString();

  if (existing) {
    await upsertUserSurveyState({
      ...existing,
      showCount: existing.showCount + 1,
      lastShownAt: now,
    });
  } else {
    await upsertUserSurveyState({
      id: `${surveyId}:${userId}`,
      surveyId,
      userId,
      productId,
      status: 'shown',
      showCount: 1,
      lastShownAt: now,
    });
  }
}

// =============================================================================
// Survey Metrics
// =============================================================================

export async function updateSurveyMetrics(
  id: string,
  productId: string,
  metrics: Partial<Survey['metrics']>
): Promise<void> {
  const container = getContainer('surveys');

  const existing = await getSurvey(id, productId);
  if (!existing) return;

  const updated: Survey = {
    ...existing,
    metrics: { ...existing.metrics, ...metrics },
    updatedAt: new Date().toISOString(),
  };

  await container.items.upsert(updated);
}

// =============================================================================
// Analytics Aggregation
// =============================================================================

export interface QuestionAnalytics {
  questionId: string;
  questionType: string;
  totalResponses: number;

  // For choice types
  optionCounts?: Record<string, number>;

  // For numeric types
  average?: number;
  min?: number;
  max?: number;
  distribution?: Record<number, number>;

  // For text types
  sampleResponses?: string[];
}

export async function getSurveyAnalytics(
  surveyId: string,
  productId: string
): Promise<{
  totalStarts: number;
  totalCompletions: number;
  completionRate: number;
  avgTimeSeconds: number;
  questionAnalytics: QuestionAnalytics[];
}> {
  const { responses } = await listResponsesForSurvey(surveyId);

  const totalStarts = responses.length;
  const completedResponses = responses.filter(r => r.isComplete);
  const totalCompletions = completedResponses.length;

  // Calculate average time
  const times = completedResponses
    .map(r => {
      if (!r.completedAt || !r.startedAt) return 0;
      return (new Date(r.completedAt).getTime() - new Date(r.startedAt).getTime()) / 1000;
    })
    .filter(t => t > 0);

  const avgTimeSeconds = times.length > 0 ? times.reduce((a, b) => a + b, 0) / times.length : 0;

  // Get survey to access question definitions
  const survey = await getSurvey(surveyId, productId);
  if (!survey) {
    return {
      totalStarts,
      totalCompletions,
      completionRate: totalStarts > 0 ? totalCompletions / totalStarts : 0,
      avgTimeSeconds,
      questionAnalytics: [],
    };
  }

  // Aggregate per-question analytics
  const questionAnalytics: QuestionAnalytics[] = survey.questions.map(q => {
    const qa: QuestionAnalytics = {
      questionId: q.id,
      questionType: q.type,
      totalResponses: 0,
    };

    const answers = responses
      .map(r => r.answers[q.id])
      .filter((a): a is QuestionAnswer => a !== undefined);

    qa.totalResponses = answers.length;

    // Type-specific aggregation
    if (q.type === 'single_choice' || q.type === 'dropdown') {
      qa.optionCounts = {};
      answers.forEach(a => {
        if (a.type === 'single_choice') {
          qa.optionCounts![a.optionId] = (qa.optionCounts![a.optionId] || 0) + 1;
        }
      });
    }

    if (q.type === 'multiple_choice') {
      qa.optionCounts = {};
      answers.forEach(a => {
        if (a.type === 'multiple_choice') {
          a.optionIds.forEach(id => {
            qa.optionCounts![id] = (qa.optionCounts![id] || 0) + 1;
          });
        }
      });
    }

    if (q.type === 'rating' || q.type === 'nps' || q.type === 'scale') {
      const values = answers
        .filter((a): a is { type: 'rating' | 'nps'; value: number } =>
          ['rating', 'nps'].includes(a.type)
        )
        .map(a => a.value);

      if (values.length > 0) {
        qa.average = values.reduce((a, b) => a + b, 0) / values.length;
        qa.min = Math.min(...values);
        qa.max = Math.max(...values);

        // Build distribution
        qa.distribution = {};
        values.forEach(v => {
          qa.distribution![v] = (qa.distribution![v] || 0) + 1;
        });
      }
    }

    if (q.type === 'text_short' || q.type === 'text_long') {
      qa.sampleResponses = answers
        .filter((a): a is { type: 'text'; value: string } => a.type === 'text')
        .map(a => a.value)
        .slice(0, 10); // Sample first 10
    }

    if (q.type === 'ranking') {
      qa.optionCounts = {};
      answers.forEach(a => {
        if (a.type === 'ranking') {
          // Count first-choice rankings
          const firstChoice = a.rankedOptionIds[0];
          if (firstChoice) {
            qa.optionCounts![firstChoice] = (qa.optionCounts![firstChoice] || 0) + 1;
          }
        }
      });
    }

    return qa;
  });

  return {
    totalStarts,
    totalCompletions,
    completionRate: totalStarts > 0 ? totalCompletions / totalStarts : 0,
    avgTimeSeconds,
    questionAnalytics,
  };
}

// =============================================================================
// CSV Export
// =============================================================================

export function exportResponsesToCSV(responses: SurveyResponse[]): string {
  if (responses.length === 0) return '';

  // Get all unique question IDs
  const questionIds = Array.from(new Set(responses.flatMap(r => Object.keys(r.answers))));

  // Build headers
  const headers = [
    'responseId',
    'userId',
    'startedAt',
    'completedAt',
    'isComplete',
    ...questionIds,
  ];

  // Build rows
  const rows = responses.map(r => {
    const base = [r.id, r.userId, r.startedAt, r.completedAt ?? '', r.isComplete ? 'yes' : 'no'];

    const answers = questionIds.map(qid => {
      const ans = r.answers[qid];
      if (!ans) return '';

      switch (ans.type) {
        case 'single_choice':
          // Also covers 'dropdown' questions (stored as single_choice answers)
          return ans.optionId;
        case 'multiple_choice':
          return ans.optionIds.join(';');
        case 'rating':
        case 'nps':
          // Also covers 'scale' questions (stored as rating answers)
          return ans.value.toString();
        case 'text':
          return ans.value.replace(/"/g, '""'); // Escape quotes for CSV
        case 'ranking':
          return ans.rankedOptionIds.join(';');
        default:
          return '';
      }
    });

    return [...base, ...answers];
  });

  // Combine
  const escape = (s: string) => (s.includes(',') || s.includes('"') ? `"${s}"` : s);
  return [headers.join(','), ...rows.map(r => r.map(escape).join(','))].join('\n');
}
