# =============================================================================
# Tests for FIMIX-PLS (Finite Mixture PLS)
# =============================================================================

# --- Setup: pre-compute models and results -----------------------------------
library(seminr)

mobi_mm <- constructs(
  composite("Image",        multi_items("IMAG", 1:5)),
  composite("Expectation",  multi_items("CUEX", 1:3)),
  composite("Value",        multi_items("PERV", 1:2)),
  composite("Satisfaction", multi_items("CUSA", 1:3)),
  composite("Loyalty",      multi_items("CUSL", 1:3))
)
mobi_sm <- relationships(
  paths(from = "Image",       to = c("Expectation", "Satisfaction", "Loyalty")),
  paths(from = "Expectation", to = c("Value", "Satisfaction")),
  paths(from = "Value",       to = "Satisfaction"),
  paths(from = "Satisfaction", to = "Loyalty")
)
pls_model <- estimate_pls(data = mobi, measurement_model = mobi_mm,
                           structural_model = mobi_sm)

# Pre-compute FIMIX for K=2 (reused across tests)
fimix_k2 <- assess_fimix(pls_model, K = 2, nstart = 5, seed = 123)

# =============================================================================
# STRUCTURE TESTS
# =============================================================================

test_that("assess_fimix returns fimix_analysis object", {
  expect_s3_class(fimix_k2, "fimix_analysis")
})

test_that("fimix result contains all expected components", {
  expected <- c("K", "segment_proportions", "segment_sizes", "posterior",
                "segment_assignment", "segment_paths", "segment_intercepts",
                "segment_variances", "log_likelihood", "n_parameters",
                "info_criteria", "converged", "iterations",
                "n_starts_completed", "pls_model", "n_obs")
  expect_true(all(expected %in% names(fimix_k2)))
})

test_that("K is stored correctly", {
  expect_equal(fimix_k2$K, 2)
})

test_that("n_obs matches model data", {
  expect_equal(fimix_k2$n_obs, nrow(pls_model$construct_scores))
})

# =============================================================================
# SEGMENT PROPORTION AND ASSIGNMENT TESTS
# =============================================================================

test_that("segment proportions sum to 1", {
  expect_equal(sum(fimix_k2$segment_proportions), 1, tolerance = 1e-10)
})

test_that("segment proportions are all positive", {
  expect_true(all(fimix_k2$segment_proportions > 0))
})

test_that("segment sizes sum to N", {
  expect_equal(sum(fimix_k2$segment_sizes), fimix_k2$n_obs)
})

test_that("segment assignments are integers from 1 to K", {
  expect_true(all(fimix_k2$segment_assignment %in% 1:2))
})

test_that("segment assignment length matches N", {
  expect_equal(length(fimix_k2$segment_assignment), fimix_k2$n_obs)
})

# =============================================================================
# POSTERIOR PROBABILITY TESTS
# =============================================================================

test_that("posterior matrix has correct dimensions", {
  expect_equal(dim(fimix_k2$posterior), c(fimix_k2$n_obs, 2))
})

test_that("posterior rows sum to 1", {
  row_sums <- rowSums(fimix_k2$posterior)
  expect_equal(row_sums, rep(1, fimix_k2$n_obs), tolerance = 1e-10)
})

test_that("posterior values are in [0, 1]", {
  expect_true(all(fimix_k2$posterior >= 0))
  expect_true(all(fimix_k2$posterior <= 1))
})

test_that("hard assignment matches argmax of posterior", {
  argmax <- apply(fimix_k2$posterior, 1, which.max)
  expect_equal(fimix_k2$segment_assignment, argmax)
})

# =============================================================================
# PATH COEFFICIENT TESTS
# =============================================================================

test_that("segment_paths is a list of length K", {
  expect_true(is.list(fimix_k2$segment_paths))
  expect_equal(length(fimix_k2$segment_paths), 2)
})

test_that("segment path matrices have same dimensions as model path_coef", {
  for (k in 1:2) {
    expect_equal(dim(fimix_k2$segment_paths[[k]]),
                 dim(pls_model$path_coef))
    expect_equal(rownames(fimix_k2$segment_paths[[k]]),
                 rownames(pls_model$path_coef))
    expect_equal(colnames(fimix_k2$segment_paths[[k]]),
                 colnames(pls_model$path_coef))
  }
})

test_that("segment path coefficients are non-zero where model has paths", {
  nonzero_mask <- pls_model$path_coef != 0
  for (k in 1:2) {
    # All structural paths in the model should have coefficients
    seg_coefs <- fimix_k2$segment_paths[[k]][nonzero_mask]
    expect_true(all(seg_coefs != 0))
  }
})

test_that("segment path coefficients are zero where model has no paths", {
  zero_mask <- pls_model$path_coef == 0
  for (k in 1:2) {
    seg_zeros <- fimix_k2$segment_paths[[k]][zero_mask]
    expect_true(all(seg_zeros == 0))
  }
})

test_that("segment intercepts have correct structure", {
  expect_equal(length(fimix_k2$segment_intercepts), 2)
  endogenous <- seminr:::all_endogenous(pls_model$smMatrix)
  for (k in 1:2) {
    expect_equal(length(fimix_k2$segment_intercepts[[k]]), length(endogenous))
    expect_true(all(names(fimix_k2$segment_intercepts[[k]]) %in% endogenous))
  }
})

# =============================================================================
# SEGMENT VARIANCE TESTS
# =============================================================================

test_that("segment_variances has correct dimensions", {
  endogenous <- seminr:::all_endogenous(pls_model$smMatrix)
  expect_equal(nrow(fimix_k2$segment_variances), length(endogenous))
  expect_equal(ncol(fimix_k2$segment_variances), 2)
})

test_that("segment variances are positive", {
  expect_true(all(fimix_k2$segment_variances > 0))
})

# =============================================================================
# INFORMATION CRITERIA TESTS
# =============================================================================

test_that("info_criteria contains expected names", {
  expected <- c("lnL", "AIC", "AIC3", "AIC4", "BIC", "CAIC", "HQ", "MDL5", "EN")
  expect_true(all(expected %in% names(fimix_k2$info_criteria)))
})

test_that("all criteria are finite", {
  expect_true(all(is.finite(fimix_k2$info_criteria)))
})

test_that("AIC < AIC3 < AIC4 (by formula)", {
  ic <- fimix_k2$info_criteria
  expect_true(ic["AIC"] < ic["AIC3"])
  expect_true(ic["AIC3"] < ic["AIC4"])
})

test_that("entropy is in [0, 1]", {
  en <- fimix_k2$info_criteria["EN"]
  expect_true(en >= 0 && en <= 1)
})

test_that("log-likelihood is negative", {
  expect_true(fimix_k2$log_likelihood < 0)
})

test_that("n_parameters is positive integer", {
  expect_true(fimix_k2$n_parameters > 0)
  expect_equal(fimix_k2$n_parameters, as.integer(fimix_k2$n_parameters))
})

# =============================================================================
# CONVERGENCE TESTS
# =============================================================================

test_that("EM converges for K=2", {
  expect_true(fimix_k2$converged)
})

test_that("iterations is positive", {
  expect_true(fimix_k2$iterations > 0)
})

test_that("n_starts_completed equals nstart", {
  expect_equal(fimix_k2$n_starts_completed, 5L)
})

# =============================================================================
# REPRODUCIBILITY TESTS
# =============================================================================

test_that("results are deterministic with same seed", {
  r1 <- assess_fimix(pls_model, K = 2, nstart = 3, seed = 42)
  r2 <- assess_fimix(pls_model, K = 2, nstart = 3, seed = 42)
  expect_equal(r1$log_likelihood, r2$log_likelihood)
  expect_equal(r1$segment_proportions, r2$segment_proportions)
  expect_equal(r1$segment_assignment, r2$segment_assignment)
})

# =============================================================================
# INPUT VALIDATION TESTS
# =============================================================================

test_that("non-seminr model returns NULL with warning", {
  expect_warning(result <- assess_fimix("not a model", K = 2),
                 "SEMinR models")
  expect_null(result)
})

test_that("K < 2 raises error", {
  expect_error(assess_fimix(pls_model, K = 1), "K must be an integer >= 2")
})

test_that("K = 0 raises error", {
  expect_error(assess_fimix(pls_model, K = 0), "K must be an integer >= 2")
})

test_that("non-integer K raises error", {
  expect_error(assess_fimix(pls_model, K = 2.5), "K must be an integer >= 2")
})

test_that("nstart < 1 raises error", {
  expect_error(assess_fimix(pls_model, K = 2, nstart = 0),
               "nstart must be an integer >= 1")
})

# =============================================================================
# S3 METHOD TESTS
# =============================================================================

test_that("print.fimix_analysis returns invisibly", {
  expect_invisible(print(fimix_k2))
})

test_that("print output contains key information", {
  out <- capture.output(print(fimix_k2))
  out_text <- paste(out, collapse = "\n")

  expect_true(grepl("FIMIX-PLS Analysis", out_text))
  expect_true(grepl("Segments: 2", out_text))
  expect_true(grepl("Converged: Yes", out_text))
  expect_true(grepl("Segment Proportions", out_text))
  expect_true(grepl("Fit Criteria", out_text))
  expect_true(grepl("Segment Path Coefficients", out_text))
})

test_that("summary returns summary.fimix_analysis object", {
  s <- summary(fimix_k2)
  expect_s3_class(s, "summary.fimix_analysis")
})

test_that("summary print includes segment details", {
  out <- capture.output(print(summary(fimix_k2)))
  out_text <- paste(out, collapse = "\n")

  expect_true(grepl("Summary", out_text))
  expect_true(grepl("Segment 1", out_text))
  expect_true(grepl("Intercept", out_text))
  expect_true(grepl("Residual Variance", out_text))
})

test_that("plot.fimix_analysis runs without error", {
  expect_no_error(plot(fimix_k2, type = "segments"))
  expect_no_error(plot(fimix_k2, type = "paths"))
})

# =============================================================================
# COMPARISON TESTS
# =============================================================================

test_that("assess_fimix_compare returns fimix_comparison object", {
  comp <- assess_fimix_compare(pls_model, K_range = 2:3,
                                 nstart = 3, seed = 123)
  expect_s3_class(comp, "fimix_comparison")
})

test_that("comparison contains solutions for each K", {
  comp <- assess_fimix_compare(pls_model, K_range = 2:3,
                                 nstart = 3, seed = 123)
  expect_true("K2" %in% names(comp$solutions))
  expect_true("K3" %in% names(comp$solutions))
})

test_that("comparison fit_table has correct structure", {
  comp <- assess_fimix_compare(pls_model, K_range = 2:3,
                                 nstart = 3, seed = 123)
  expect_true("K" %in% names(comp$fit_table))
  expect_true("AIC" %in% names(comp$fit_table))
  expect_true("BIC" %in% names(comp$fit_table))
  expect_true("EN" %in% names(comp$fit_table))
  expect_equal(nrow(comp$fit_table), 2)
})

test_that("comparison print shows best K by criterion", {
  comp <- assess_fimix_compare(pls_model, K_range = 2:3,
                                 nstart = 3, seed = 123)
  out <- capture.output(print(comp))
  out_text <- paste(out, collapse = "\n")

  expect_true(grepl("FIMIX-PLS Segment Selection", out_text))
  expect_true(grepl("Best K by criterion", out_text))
})

test_that("comparison plot runs without error", {
  comp <- assess_fimix_compare(pls_model, K_range = 2:3,
                                 nstart = 3, seed = 123)
  expect_no_error(plot(comp, type = "criteria"))
  expect_no_error(plot(comp, type = "entropy"))
})

test_that("comparison K_range validation works", {
  expect_error(assess_fimix_compare(pls_model, K_range = 1),
               "K_range must be a vector of integers >= 2")
})

# =============================================================================
# MODEL TYPE TESTS
# =============================================================================

test_that("FIMIX works with mediation model", {
  skip_on_cran()
  mm_med <- constructs(
    composite("Image",        multi_items("IMAG", 1:5)),
    composite("Expectation",  multi_items("CUEX", 1:3)),
    composite("Satisfaction", multi_items("CUSA", 1:3)),
    composite("Loyalty",      multi_items("CUSL", 1:3))
  )
  sm_med <- relationships(
    paths(from = "Image", to = c("Expectation", "Loyalty")),
    paths(from = "Expectation", to = "Satisfaction"),
    paths(from = "Satisfaction", to = "Loyalty")
  )
  pls_med <- estimate_pls(data = mobi, measurement_model = mm_med,
                            structural_model = sm_med)

  result <- assess_fimix(pls_med, K = 2, nstart = 3, seed = 42)

  expect_s3_class(result, "fimix_analysis")
  expect_true(result$converged)
  expect_equal(sum(result$segment_sizes), nrow(mobi))
})

test_that("FIMIX works with moderation model (two-stage)", {
  skip_on_cran()
  mm_mod <- constructs(
    composite("Image",        multi_items("IMAG", 1:5)),
    composite("Value",        multi_items("PERV", 1:2)),
    composite("Satisfaction", multi_items("CUSA", 1:3)),
    composite("Loyalty",      multi_items("CUSL", 1:3)),
    interaction_term(iv = "Image", moderator = "Value", method = two_stage)
  )
  sm_mod <- relationships(
    paths(from = c("Image", "Value", "Image*Value"), to = "Satisfaction"),
    paths(from = "Satisfaction", to = "Loyalty")
  )
  pls_mod <- estimate_pls(data = mobi, measurement_model = mm_mod,
                            structural_model = sm_mod)

  result <- assess_fimix(pls_mod, K = 2, nstart = 3, seed = 42)

  expect_s3_class(result, "fimix_analysis")
  expect_true(result$converged)
  # Interaction construct should be a predictor in segment paths
  expect_true(any(result$segment_paths[[1]]["Image*Value", ] != 0))
})

test_that("FIMIX works with HOC two-stage model", {
  skip_on_cran()
  mm_hoc <- constructs(
    composite("Image",        multi_items("IMAG", 1:5)),
    composite("Expectation",  multi_items("CUEX", 1:3)),
    higher_composite("Quality", c("Image", "Expectation"), method = two_stage),
    composite("Satisfaction", multi_items("CUSA", 1:3)),
    composite("Loyalty",      multi_items("CUSL", 1:3))
  )
  sm_hoc <- relationships(
    paths(from = "Quality", to = c("Satisfaction", "Loyalty")),
    paths(from = "Satisfaction", to = "Loyalty")
  )
  pls_hoc <- estimate_pls(data = mobi, measurement_model = mm_hoc,
                            structural_model = sm_hoc)

  result <- assess_fimix(pls_hoc, K = 2, nstart = 3, seed = 42)

  expect_s3_class(result, "fimix_analysis")
  expect_true(result$converged)
  expect_true("Quality" %in% rownames(result$segment_paths[[1]]))
})

# =============================================================================
# SEGMENT DIFFERENTIATION TESTS
# =============================================================================

test_that("segments have different path coefficients", {
  # At least some paths should differ between segments
  diffs <- abs(fimix_k2$segment_paths[[1]] - fimix_k2$segment_paths[[2]])
  nonzero_mask <- pls_model$path_coef != 0
  max_diff <- max(diffs[nonzero_mask])
  expect_true(max_diff > 0.01)
})
