'use strict';

const health = require('./health');
const tracker = require('./tracker');
const versioning = require('./versioning');

const DAY_IN_MS = 24 * 60 * 60 * 1000;
const SPARKLINE_CHARS = '\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588';
const EMPTY_BLOCK = '\u2591';
const FILL_BLOCK = '\u2588';
const DEFAULT_PANEL_WIDTH = 64;
const VALID_PANELS = new Set(['success-rate', 'failures', 'amendments', 'versions']);

function sparkline(values) {
  if (!Array.isArray(values) || values.length === 0) {
    return '';
  }

  return values.map(value => {
    if (value === null || value === undefined) {
      return EMPTY_BLOCK;
    }

    const clamped = Math.max(0, Math.min(1, value));
    const index = Math.min(Math.round(clamped * (SPARKLINE_CHARS.length - 1)), SPARKLINE_CHARS.length - 1);
    return SPARKLINE_CHARS[index];
  }).join('');
}

function horizontalBar(value, max, width) {
  if (max <= 0 || width <= 0) {
    return EMPTY_BLOCK.repeat(width || 0);
  }

  const filled = Math.round((Math.min(value, max) / max) * width);
  const empty = width - filled;
  return FILL_BLOCK.repeat(filled) + EMPTY_BLOCK.repeat(empty);
}

function panelBox(title, lines, width) {
  const innerWidth = width || DEFAULT_PANEL_WIDTH;
  const output = [];
  output.push('\u250C\u2500 ' + title + ' ' + '\u2500'.repeat(Math.max(0, innerWidth - title.length - 4)) + '\u2510');

  for (const line of lines) {
    const truncated = line.length > innerWidth - 2
      ? line.slice(0, innerWidth - 2)
      : line;
    output.push('\u2502 ' + truncated.padEnd(innerWidth - 2) + '\u2502');
  }

  output.push('\u2514' + '\u2500'.repeat(innerWidth - 1) + '\u2518');
  return output.join('\n');
}

function bucketByDay(records, nowMs, days) {
  const buckets = [];
  for (let i = days - 1; i >= 0; i -= 1) {
    const dayEnd = nowMs - (i * DAY_IN_MS);
    const dayStart = dayEnd - DAY_IN_MS;
    const dateStr = new Date(dayEnd).toISOString().slice(0, 10);
    buckets.push({ date: dateStr, start: dayStart, end: dayEnd, records: [] });
  }

  for (const record of records) {
    const recordMs = Date.parse(record.recorded_at);
    if (Number.isNaN(recordMs)) {
      continue;
    }

    for (const bucket of buckets) {
      if (recordMs > bucket.start && recordMs <= bucket.end) {
        bucket.records.push(record);
        break;
      }
    }
  }

  return buckets.map(bucket => ({
    date: bucket.date,
    rate: bucket.records.length > 0
      ? health.calculateSuccessRate(bucket.records)
      : null,
    runs: bucket.records.length,
  }));
}

function getTrendArrow(successRate7d, successRate30d) {
  if (successRate7d === null || successRate30d === null) {
    return '\u2192';
  }

  const delta = successRate7d - successRate30d;
  if (delta >= 0.1) {
    return '\u2197';
  }

  if (delta <= -0.1) {
    return '\u2198';
  }

  return '\u2192';
}

function formatPercent(value) {
  if (value === null) {
    return 'n/a';
  }

  return `${Math.round(value * 100)}%`;
}

function groupRecordsBySkill(records) {
  return records.reduce((grouped, record) => {
    const skillId = record.skill_id;
    if (!grouped.has(skillId)) {
      grouped.set(skillId, []);
    }

    grouped.get(skillId).push(record);
    return grouped;
  }, new Map());
}

function renderSuccessRatePanel(records, skills, options = {}) {
  const nowMs = Date.parse(options.now || new Date().toISOString());
  const days = options.days || 30;
  const width = options.width || DEFAULT_PANEL_WIDTH;
  const recordsBySkill = groupRecordsBySkill(records);

  const skillData = [];
  const skillIds = Array.from(new Set([
    ...Array.from(recordsBySkill.keys()),
    ...skills.map(s => s.skill_id),
  ])).sort();

  for (const skillId of skillIds) {
    const skillRecords = recordsBySkill.get(skillId) || [];
    const dailyRates = bucketByDay(skillRecords, nowMs, days);
    const rateValues = dailyRates.map(b => b.rate);
    const records7d = health.filterRecordsWithinDays(skillRecords, nowMs, 7);
    const records30d = health.filterRecordsWithinDays(skillRecords, nowMs, 30);
    const current7d = health.calculateSuccessRate(records7d);
    const current30d = health.calculateSuccessRate(records30d);
    const trend = getTrendArrow(current7d, current30d);

    skillData.push({
      skill_id: skillId,
      daily_rates: dailyRates,
      sparkline: sparkline(rateValues),
      current_7d: current7d,
      trend,
    });
  }

  const lines = [];
  if (skillData.length === 0) {
    lines.push('No skill execution data available.');
  } else {
    for (const skill of skillData) {
      const nameCol = skill.skill_id.slice(0, 14).padEnd(14);
      const sparkCol = skill.sparkline.slice(0, 30);
      const rateCol = formatPercent(skill.current_7d).padStart(5);
      lines.push(`${nameCol}  ${sparkCol}  ${rateCol} ${skill.trend}`);
    }
  }

  return {
    text: panelBox('Success Rate (30d)', lines, width),
    data: { skills: skillData },
  };
}

function renderFailureClusterPanel(records, options = {}) {
  const width = options.width || DEFAULT_PANEL_WIDTH;
  const failures = records.filter(r => r.outcome === 'failure');

  const clusterMap = new Map();
  for (const record of failures) {
    const reason = (record.failure_reason || 'unknown').toLowerCase().trim();
    if (!clusterMap.has(reason)) {
      clusterMap.set(reason, { count: 0, skill_ids: new Set() });
    }

    const cluster = clusterMap.get(reason);
    cluster.count += 1;
    cluster.skill_ids.add(record.skill_id);
  }

  const clusters = Array.from(clusterMap.entries())
    .map(([pattern, data]) => ({
      pattern,
      count: data.count,
      skill_ids: Array.from(data.skill_ids).sort(),
      percentage: failures.length > 0
        ? Math.round((data.count / failures.length) * 100)
        : 0,
    }))
    .sort((a, b) => b.count - a.count || a.pattern.localeCompare(b.pattern));

  const maxCount = clusters.length > 0 ? clusters[0].count : 0;
  const lines = [];

  if (clusters.length === 0) {
    lines.push('No failure patterns detected.');
  } else {
    for (const cluster of clusters) {
      const label = cluster.pattern.slice(0, 20).padEnd(20);
      const bar = horizontalBar(cluster.count, maxCount, 16);
      const skillCount = cluster.skill_ids.length;
      const suffix = skillCount === 1 ? 'skill' : 'skills';
      lines.push(`${label} ${bar} ${String(cluster.count).padStart(3)} (${skillCount} ${suffix})`);
    }
  }

  return {
    text: panelBox('Failure Patterns', lines, width),
    data: { clusters, total_failures: failures.length },
  };
}

function renderAmendmentPanel(skillsById, options = {}) {
  const width = options.width || DEFAULT_PANEL_WIDTH;
  const amendments = [];

  for (const [skillId, skill] of skillsById) {
    if (!skill.skill_dir) {
      continue;
    }

    const log = versioning.getEvolutionLog(skill.skill_dir, 'amendments');
    for (const entry of log) {
      const status = typeof entry.status === 'string' ? entry.status : null;
      const isPending = status
        ? health.PENDING_AMENDMENT_STATUSES.has(status)
        : entry.event === 'proposal';

      if (isPending) {
        amendments.push({
          skill_id: skillId,
          event: entry.event || 'proposal',
          status: status || 'pending',
          created_at: entry.created_at || null,
        });
      }
    }
  }

  amendments.sort((a, b) => {
    const timeA = a.created_at ? Date.parse(a.created_at) : 0;
    const timeB = b.created_at ? Date.parse(b.created_at) : 0;
    return timeB - timeA;
  });

  const lines = [];
  if (amendments.length === 0) {
    lines.push('No pending amendments.');
  } else {
    for (const amendment of amendments) {
      const name = amendment.skill_id.slice(0, 14).padEnd(14);
      const event = amendment.event.padEnd(10);
      const status = amendment.status.padEnd(10);
      const time = amendment.created_at ? amendment.created_at.slice(0, 19) : '-';
      lines.push(`${name} ${event} ${status} ${time}`);
    }

    lines.push('');
    lines.push(`${amendments.length} amendment${amendments.length === 1 ? '' : 's'} pending review`);
  }

  return {
    text: panelBox('Pending Amendments', lines, width),
    data: { amendments, total: amendments.length },
  };
}

function renderVersionTimelinePanel(skillsById, options = {}) {
  const width = options.width || DEFAULT_PANEL_WIDTH;
  const skillVersions = [];

  for (const [skillId, skill] of skillsById) {
    if (!skill.skill_dir) {
      continue;
    }

    const versions = versioning.listVersions(skill.skill_dir);
    if (versions.length === 0) {
      continue;
    }

    const amendmentLog = versioning.getEvolutionLog(skill.skill_dir, 'amendments');
    const reasonByVersion = new Map();
    for (const entry of amendmentLog) {
      if (entry.version && entry.reason) {
        reasonByVersion.set(entry.version, entry.reason);
      }
    }

    skillVersions.push({
      skill_id: skillId,
      versions: versions.map(v => ({
        version: v.version,
        created_at: v.created_at,
        reason: reasonByVersion.get(v.version) || null,
      })),
    });
  }

  skillVersions.sort((a, b) => a.skill_id.localeCompare(b.skill_id));

  const lines = [];
  if (skillVersions.length === 0) {
    lines.push('No version history available.');
  } else {
    for (const skill of skillVersions) {
      lines.push(skill.skill_id);
      for (const version of skill.versions) {
        const date = version.created_at ? version.created_at.slice(0, 10) : '-';
        const reason = version.reason || '-';
        lines.push(`  v${version.version} \u2500\u2500 ${date} \u2500\u2500 ${reason}`);
      }
    }
  }

  return {
    text: panelBox('Version History', lines, width),
    data: { skills: skillVersions },
  };
}

function renderDashboard(options = {}) {
  const now = options.now || new Date().toISOString();
  const nowMs = Date.parse(now);
  if (Number.isNaN(nowMs)) {
    throw new Error(`Invalid now timestamp: ${now}`);
  }

  const dashboardOptions = { ...options, now };
  const records = tracker.readSkillExecutionRecords(dashboardOptions);
  const skillsById = health.discoverSkills(dashboardOptions);
  const report = health.collectSkillHealth(dashboardOptions);
  const summary = health.summarizeHealthReport(report);

  const panelRenderers = {
    'success-rate': () => renderSuccessRatePanel(records, report.skills, dashboardOptions),
    'failures': () => renderFailureClusterPanel(records, dashboardOptions),
    'amendments': () => renderAmendmentPanel(skillsById, dashboardOptions),
    'versions': () => renderVersionTimelinePanel(skillsById, dashboardOptions),
  };

  const selectedPanel = options.panel || null;
  if (selectedPanel && !VALID_PANELS.has(selectedPanel)) {
    throw new Error(`Unknown panel: ${selectedPanel}. Valid panels: ${Array.from(VALID_PANELS).join(', ')}`);
  }

  const panels = {};
  const textParts = [];

  const header = [
    'ECC Skill Health Dashboard',
    `Generated: ${now}`,
    `Skills: ${summary.total_skills} total, ${summary.healthy_skills} healthy, ${summary.declining_skills} declining`,
    '',
  ];

  textParts.push(header.join('\n'));

  if (selectedPanel) {
    const result = panelRenderers[selectedPanel]();
    panels[selectedPanel] = result.data;
    textParts.push(result.text);
  } else {
    for (const [panelName, renderer] of Object.entries(panelRenderers)) {
      const result = renderer();
      panels[panelName] = result.data;
      textParts.push(result.text);
    }
  }

  const text = textParts.join('\n\n') + '\n';
  const data = {
    generated_at: now,
    summary,
    panels,
  };

  return { text, data };
}

module.exports = {
  VALID_PANELS,
  bucketByDay,
  horizontalBar,
  panelBox,
  renderAmendmentPanel,
  renderDashboard,
  renderFailureClusterPanel,
  renderSuccessRatePanel,
  renderVersionTimelinePanel,
  sparkline,
};
