'use strict';

const path = require('path');

const MANAGED_FILE_HEALTH_CODES = new Set([
  'missing-target-root',
  'unsafe-managed-destination',
  'unsafe-repair-source',
  'missing-managed-files',
  'drifted-managed-files',
  'missing-source-files',
  'unverified-managed-operations',
]);

function cloneJsonValue(value) {
  return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
}

function buildInstallStateStoreRecord(state) {
  if (!state || !state.target || !state.request || !state.resolution || !state.source) {
    throw new Error('Invalid canonical install-state: required projection fields are missing');
  }

  return {
    targetId: state.target.id,
    targetRoot: state.target.root,
    profile: state.request.profile ?? null,
    modules: Array.isArray(state.resolution.selectedModules)
      ? [...state.resolution.selectedModules]
      : [],
    operations: Array.isArray(state.operations)
      ? state.operations.map(operation => cloneJsonValue(operation))
      : [],
    installedAt: state.installedAt,
    sourceVersion: state.source.repoVersion ?? null,
  };
}

function warningFor(record, code, message) {
  return {
    code,
    message,
    targetId: record && record.adapter ? record.adapter.id : null,
    targetRoot: record && record.targetRoot ? record.targetRoot : null,
    installStatePath: record && record.installStatePath ? record.installStatePath : null,
  };
}

function getRecordIdentity(record) {
  if (
    !record
    || !record.adapter
    || typeof record.adapter.id !== 'string'
    || record.adapter.id.length === 0
    || typeof record.targetRoot !== 'string'
    || record.targetRoot.length === 0
  ) {
    return null;
  }

  return {
    targetId: record.adapter.id,
    targetRoot: record.targetRoot,
  };
}

function pathsMatch(left, right) {
  return typeof left === 'string'
    && left.length > 0
    && typeof right === 'string'
    && right.length > 0
    && path.resolve(left) === path.resolve(right);
}

function stateMatchesDiscoveryRecord(state, record) {
  return Boolean(
    state
    && state.target
    && state.target.id === record.adapter.id
    && pathsMatch(state.target.root, record.targetRoot)
    && pathsMatch(state.target.installStatePath, record.installStatePath)
  );
}

function projectInstallState(store, state) {
  try {
    const record = buildInstallStateStoreRecord(state);
    store.upsertInstallState(record);
    return {
      status: 'projected',
      record,
      warning: null,
    };
  } catch (error) {
    return {
      status: 'warning',
      record: null,
      warning: {
        code: 'projection-write-failed',
        message: error.message,
        targetId: state && state.target ? state.target.id : null,
        targetRoot: state && state.target ? state.target.root : null,
        installStatePath: state && state.target ? state.target.installStatePath : null,
      },
    };
  }
}

function removeInstallStateProjection(store, identity) {
  try {
    return {
      status: 'removed',
      removed: store.deleteInstallState(identity),
      warning: null,
    };
  } catch (error) {
    return {
      status: 'warning',
      removed: false,
      warning: {
        code: 'projection-delete-failed',
        message: error.message,
        targetId: identity && identity.targetId ? identity.targetId : null,
        targetRoot: identity && identity.targetRoot ? identity.targetRoot : null,
        installStatePath: null,
      },
    };
  }
}

function createReconciliationResult(discoveredCount) {
  return {
    status: 'ok',
    discoveredCount,
    projectedCount: 0,
    removedCount: 0,
    warningCount: 0,
    warnings: [],
    scopedTargets: [],
    managedFileHealth: [],
  };
}

function addWarning(result, warning) {
  return {
    ...result,
    status: 'warning',
    warningCount: result.warningCount + 1,
    warnings: [...result.warnings, warning],
  };
}

function removeDiscoverableProjection(store, identity, result) {
  const removal = removeInstallStateProjection(store, identity);
  if (removal.warning) {
    return addWarning(result, removal.warning);
  }
  return {
    ...result,
    removedCount: result.removedCount + (removal.removed ? 1 : 0),
  };
}

/**
 * Reconcile only the identities enumerated by install target discovery.
 * Canonical JSON files remain authoritative, and rows from other home or
 * project scopes are deliberately left unchanged.
 */
function reconcileInstallStateProjections(store, discoveryRecords) {
  const records = Array.isArray(discoveryRecords) ? discoveryRecords : [];
  let result = {
    ...createReconciliationResult(records.length),
    scopedTargets: records.map(getRecordIdentity).filter(Boolean),
  };

  for (const record of records) {
    const identity = getRecordIdentity(record);
    if (!identity) {
      result = addWarning(result, warningFor(
        record,
        'invalid-discovery-record',
        'Install target discovery returned an invalid target identity'
      ));
      continue;
    }

    if (!record.exists) {
      result = removeDiscoverableProjection(store, identity, result);
      continue;
    }

    if (record.error || !record.state) {
      result = removeDiscoverableProjection(store, identity, result);
      result = addWarning(result, warningFor(
        record,
        'invalid-install-state',
        record.error || 'Canonical install-state could not be read'
      ));
      continue;
    }

    if (!stateMatchesDiscoveryRecord(record.state, record)) {
      result = removeDiscoverableProjection(store, identity, result);
      result = addWarning(result, warningFor(
        record,
        'install-state-identity-mismatch',
        'Canonical install-state identity does not match its discovered target'
      ));
      continue;
    }

    const projection = projectInstallState(store, record.state);
    if (projection.warning) {
      result = addWarning(result, projection.warning);
      continue;
    }
    result = {
      ...result,
      projectedCount: result.projectedCount + 1,
    };
  }

  return result;
}

function inspectManagedFileHealth(options) {
  const buildReport = options.buildDoctorReport
    || require('../install-lifecycle').buildDoctorReport;
  const report = buildReport({
    repoRoot: options.repoRoot,
    homeDir: options.homeDir,
    projectRoot: options.projectRoot,
    targets: options.targets,
  });

  return report.results.map(result => {
    const issues = Array.isArray(result.issues)
      ? result.issues.filter(issue => MANAGED_FILE_HEALTH_CODES.has(issue.code))
      : [];
    const status = issues.some(issue => issue.severity === 'error')
      ? 'error'
      : issues.some(issue => issue.severity === 'warning') ? 'warning' : 'ok';
    return {
      targetId: result.adapter.id,
      targetRoot: result.targetRoot,
      status,
      issues: issues.map(issue => cloneJsonValue(issue)),
    };
  });
}

function identityKey(identity) {
  return `${identity.targetId}\u0000${path.resolve(identity.targetRoot)}`;
}

function summarizeProjectedInstallHealth(installHealth, reconciliation) {
  const healthEntries = Array.isArray(reconciliation && reconciliation.managedFileHealth)
    ? reconciliation.managedFileHealth
    : [];
  const healthByIdentity = new Map(healthEntries.map(entry => [identityKey(entry), entry]));
  const healthCheckFailed = Boolean(
    reconciliation
    && Array.isArray(reconciliation.warnings)
    && reconciliation.warnings.some(warning => warning.code === 'install-health-check-failed')
  );
  const scopedIdentities = new Set(
    Array.isArray(reconciliation && reconciliation.scopedTargets)
      ? reconciliation.scopedTargets.map(identityKey)
      : []
  );
  const installations = installHealth.installations.map(installation => {
    const key = identityKey(installation);
    const canonicalHealth = healthByIdentity.get(key);
    if (canonicalHealth) {
      return {
        ...installation,
        status: canonicalHealth.status === 'ok' ? installation.status : 'warning',
        canonicalStatus: canonicalHealth.status,
        issues: canonicalHealth.issues.map(issue => cloneJsonValue(issue)),
      };
    }

    if (healthCheckFailed && scopedIdentities.has(key)) {
      return {
        ...installation,
        status: 'warning',
        canonicalStatus: 'unverified',
        issues: [{
          severity: 'warning',
          code: 'install-health-check-failed',
          message: 'Canonical managed-file health could not be verified',
        }],
      };
    }

    return installation;
  });
  const healthyCount = installations.filter(installation => installation.status === 'healthy').length;
  const warningCount = installations.length - healthyCount;

  return {
    ...installHealth,
    status: installations.length === 0
      ? 'missing'
      : warningCount > 0 ? 'warning' : 'healthy',
    healthyCount,
    warningCount,
    installations,
  };
}

function reconcileCurrentInstallState(store, options = {}) {
  try {
    const discover = options.discoverInstalledStates
      || require('../install-lifecycle').discoverInstalledStates;
    const records = discover({
      homeDir: options.homeDir,
      projectRoot: options.projectRoot,
      targets: options.targets,
    });
    let result = reconcileInstallStateProjections(store, records);
    try {
      result = {
        ...result,
        managedFileHealth: inspectManagedFileHealth(options),
      };
    } catch (error) {
      result = addWarning(result, {
        code: 'install-health-check-failed',
        message: error.message,
        targetId: null,
        targetRoot: null,
        installStatePath: null,
      });
    }
    return result;
  } catch (error) {
    return {
      ...createReconciliationResult(0),
      status: 'warning',
      warningCount: 1,
      warnings: [{
        code: 'install-state-discovery-failed',
        message: error.message,
        targetId: null,
        targetRoot: null,
        installStatePath: null,
      }],
    };
  }
}

module.exports = {
  buildInstallStateStoreRecord,
  projectInstallState,
  reconcileCurrentInstallState,
  reconcileInstallStateProjections,
  removeInstallStateProjection,
  stateMatchesDiscoveryRecord,
  summarizeProjectedInstallHealth,
};
