import type { ProjectAiProviderMessage } from '@learnitive/project-ai';
import { projectFilePath } from '@learnitive/project-storage';
import type { ProjectAiService, ProjectStorageService } from '@learnitive/service-core';

export type CodeReviewMode = 'quick' | 'full' | 'security';
export type CodeReviewSeverity = 'critical' | 'high' | 'medium' | 'low';

export type CodeReviewFinding = {
  id: string;
  severity: CodeReviewSeverity;
  category: string;
  filePath: string;
  line?: number;
  title: string;
  explanation: string;
  originalText?: string;
  replacementText?: string;
};

export type CodeReviewResult = {
  summary: string;
  findings: readonly CodeReviewFinding[];
};

export type CodeSuggestion = {
  from: number;
  to: number;
  originalText: string;
  replacementText: string;
  reason: string;
  confidence: number;
};

const TEXT_EXTENSIONS = /\.(?:c|cc|cpp|cxx|h|hh|hpp|hxx|css|go|html?|java|js|jsx|json|mjs|cjs|php|py|r|rb|rs|sh|ts|tsx|vue|svelte|toml|yaml|yml)$/i;
const BLOCKED_PATH = /(?:^|\/)(?:\.git|node_modules|dist|build|coverage|\.learnitive-tmp|\.learnitive)(?:\/|$)/i;
const SENSITIVE_NAME = /(?:^|\/)(?:\.env(?:\.|$)|.*(?:secret|credential|token|private[-_.]?key).*)/i;
const MAX_FILE_CHARS = 32_000;
const MAX_CONTEXT_CHARS = 150_000;
const MAX_FILES = 24;

function cleanJson(raw: string): unknown {
  const trimmed = raw.trim().replace(/^```(?:json)?\s*/i, '').replace(/\s*```$/i, '');
  const start = Math.min(...['{', '['].map((char) => { const index = trimmed.indexOf(char); return index < 0 ? Number.POSITIVE_INFINITY : index; }));
  if (!Number.isFinite(start)) throw new Error('AI returned no structured result.');
  const candidate = trimmed.slice(start);
  const objectEnd = candidate.lastIndexOf('}');
  const arrayEnd = candidate.lastIndexOf(']');
  const end = Math.max(objectEnd, arrayEnd);
  if (end < 0) throw new Error('AI returned incomplete structured output.');
  return JSON.parse(candidate.slice(0, end + 1));
}

function stringValue(value: unknown, max = 4000): string {
  return typeof value === 'string' ? value.trim().slice(0, max) : '';
}

function severityValue(value: unknown): CodeReviewSeverity {
  return value === 'critical' || value === 'high' || value === 'medium' || value === 'low' ? value : 'medium';
}

function numberValue(value: unknown): number | undefined {
  return typeof value === 'number' && Number.isFinite(value) ? Math.max(1, Math.trunc(value)) : undefined;
}

export async function collectCodeProjectContext(projectStorage: ProjectStorageService, wsName: string, currentFile?: string): Promise<string> {
  const provider = await projectStorage.getProvider(wsName);
  const entries = (await provider.listEntries())
    .filter((entry) => entry.kind === 'file' && TEXT_EXTENSIONS.test(entry.path) && !BLOCKED_PATH.test(entry.path) && !SENSITIVE_NAME.test(entry.path))
    .sort((a, b) => {
      if (currentFile && a.path === currentFile) return -1;
      if (currentFile && b.path === currentFile) return 1;
      return a.path.localeCompare(b.path);
    })
    .slice(0, MAX_FILES);
  const blocks: string[] = [];
  let total = 0;
  for (const entry of entries) {
    if (entry.kind !== 'file' || total >= MAX_CONTEXT_CHARS) break;
    const bytes = await provider.readFile(projectFilePath(entry.path));
    if (!bytes) continue;
    const text = new TextDecoder('utf-8', { fatal: false }).decode(bytes).slice(0, MAX_FILE_CHARS);
    const block = `FILE: ${entry.path}\n${text}`;
    const remaining = MAX_CONTEXT_CHARS - total;
    blocks.push(block.slice(0, remaining));
    total += Math.min(block.length, remaining);
  }
  return blocks.join('\n\n---\n\n');
}

export async function runCodeReview(input: {
  projectAi: ProjectAiService;
  projectStorage: ProjectStorageService;
  wsName: string;
  currentFile?: string;
  mode: CodeReviewMode;
  diagnostics?: string;
  signal: AbortSignal;
}): Promise<CodeReviewResult> {
  const context = await collectCodeProjectContext(input.projectStorage, input.wsName, input.currentFile);
  if (!context.trim()) throw new Error('No reviewable code files were found in this project.');
  const emphasis = input.mode === 'quick'
    ? 'Prioritize obvious correctness bugs, API misuse, type errors, and small high-confidence maintainability issues. Return at most 8 findings.'
    : input.mode === 'security'
      ? 'Prioritize injection, unsafe input handling, authentication/authorization mistakes, secrets exposure, command/path traversal, dependency/configuration risks, and insecure browser/server patterns. Return at most 12 findings.'
      : 'Review correctness, edge cases, maintainability, architecture, language/framework idioms, performance, type safety, error handling, security, and test gaps. Return at most 15 findings.';
  const messages: ProjectAiProviderMessage[] = [
    {
      role: 'system',
      content: [
        'You are Learnitive Code Review. Review software source code, never a paper or manuscript.',
        'Do not discuss citations, literature, academic argumentation, research evidence, or paper structure.',
        emphasis,
        'Return JSON only with shape: {"summary":"...","findings":[{"severity":"critical|high|medium|low","category":"correctness|security|performance|maintainability|types|testing|api|other","filePath":"path","line":1,"title":"short","explanation":"specific explanation","originalText":"exact source snippet when a safe edit is possible","replacementText":"replacement snippet when a safe edit is possible"}]}',
        'Only include originalText/replacementText if originalText is copied exactly from the provided source and the replacement is a small, safe edit. Never invent files.',
      ].join('\n'),
    },
    { role: 'user', content: `PROJECT SOURCE:\n${context}${input.diagnostics?.trim() ? `\n\nTRUSTED COMPILER/RUNTIME DIAGNOSTICS:\n${input.diagnostics.trim().slice(0, 12_000)}` : ''}` },
  ];
  const raw = await input.projectAi.generateConfigured(messages, input.signal, undefined, 'interactive');
  const parsed = cleanJson(raw);
  const record = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, unknown> : {};
  const findingsRaw = Array.isArray(record.findings) ? record.findings : [];
  const findings: CodeReviewFinding[] = [];
  for (const [index, item] of findingsRaw.entries()) {
    if (!item || typeof item !== 'object' || Array.isArray(item)) continue;
    const value = item as Record<string, unknown>;
    const filePath = stringValue(value.filePath, 400).replace(/^\/+/, '');
    const title = stringValue(value.title, 240);
    const explanation = stringValue(value.explanation, 2000);
    if (!filePath || !title || !explanation || BLOCKED_PATH.test(filePath) || SENSITIVE_NAME.test(filePath)) continue;
    const originalText = stringValue(value.originalText, 4000);
    const replacementText = stringValue(value.replacementText, 4000);
    findings.push({
      id: `code-review-${Date.now()}-${index}`,
      severity: severityValue(value.severity),
      category: stringValue(value.category, 80) || 'other',
      filePath,
      ...(numberValue(value.line) ? { line: numberValue(value.line) } : {}),
      title,
      explanation,
      ...(originalText && replacementText && originalText !== replacementText ? { originalText, replacementText } : {}),
    });
  }
  return { summary: stringValue(record.summary, 2000) || 'Code review complete.', findings };
}

export async function generateAutomaticCodeSuggestion(input: {
  projectAi: ProjectAiService;
  filePath: string;
  language: string;
  source: string;
  cursor: number;
  diagnostics?: string;
  signal: AbortSignal;
}): Promise<CodeSuggestion | undefined> {
  const source = input.source;
  if (!source.trim() || source.length > 250_000) return undefined;
  const cursor = Math.max(0, Math.min(input.cursor, source.length));
  const lineStart = source.lastIndexOf('\n', Math.max(0, cursor - 1)) + 1;
  const nextNewline = source.indexOf('\n', cursor);
  const lineEnd = nextNewline < 0 ? source.length : nextNewline;
  const originalText = source.slice(lineStart, lineEnd);
  if (!originalText.trim() || originalText.length > 1200) return undefined;
  const contextStart = Math.max(0, lineStart - 4000);
  const contextEnd = Math.min(source.length, lineEnd + 4000);
  const messages: ProjectAiProviderMessage[] = [
    {
      role: 'system',
      content: [
        'You are Learnitive automatic code suggestion.',
        'This is source code, not a manuscript. Never discuss citations, papers, literature, prose style, or academic writing.',
        'Make at most one small, high-confidence code edit to the exact target line. Prefer correctness, type safety, defensive checks, or idiomatic API use. If no useful change exists, return the original line unchanged.',
        'Return JSON only: {"replacementText":"exact replacement line","reason":"short code-specific reason","confidence":0.0}.',
        'Do not use Markdown fences.',
      ].join('\n'),
    },
    { role: 'user', content: `FILE: ${input.filePath}\nLANGUAGE: ${input.language}\nEXACT_TARGET_LINE:\n${originalText}\n\nNEARBY_CODE:\n${source.slice(contextStart, contextEnd)}${input.diagnostics?.trim() ? `\n\nRECENT TRUSTED COMPILER/RUNTIME DIAGNOSTICS:\n${input.diagnostics.trim().slice(0, 4_000)}` : ''}` },
  ];
  const raw = await input.projectAi.generateConfigured(messages, input.signal, undefined, 'background');
  const parsed = cleanJson(raw);
  const record = parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, unknown> : {};
  const replacementText = stringValue(record.replacementText, 2400);
  const confidence = typeof record.confidence === 'number' && Number.isFinite(record.confidence) ? Math.max(0, Math.min(1, record.confidence)) : 0;
  if (!replacementText || replacementText === originalText || confidence < 0.82 || replacementText.includes('```')) return undefined;
  if (replacementText.length > Math.max(2400, originalText.length * 4)) return undefined;
  return { from: lineStart, to: lineEnd, originalText, replacementText, reason: stringValue(record.reason, 500) || 'Automatic code improvement', confidence };
}
