package app

import (
	"context"
	"regexp"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
)

func TestReplaceGrantsClosesRemovedHistoryAndCreatesNewInterval(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()

	mock.ExpectBegin()
	tx, err := db.Begin()
	if err != nil {
		t.Fatal(err)
	}
	mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM platform_user_menu WHERE user_id=?`)).
		WithArgs(uint64(7)).
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO platform_user_menu(user_id,menu_key,granted_by) VALUES(?,?,?)`)).
		WithArgs(uint64(7), "monitor", "admin-a").
		WillReturnResult(sqlmock.NewResult(1, 1))
	validFrom := time.Date(2026, 6, 5, 0, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*60*60))
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.vin,COALESCE(p.valid_from,p.granted_at),p.valid_to,
COALESCE(h.id,0),h.valid_from,h.valid_to
FROM platform_user_vehicle p
LEFT JOIN platform_user_vehicle_grant_history h
  ON h.id=(SELECT MAX(latest.id) FROM platform_user_vehicle_grant_history latest WHERE latest.user_id=p.user_id AND latest.vin=p.vin)
WHERE p.user_id=? FOR UPDATE`)).
		WithArgs(uint64(7)).
		WillReturnRows(sqlmock.NewRows([]string{"vin", "valid_from", "valid_to", "history_id", "history_valid_from", "history_valid_to"}).AddRow("VIN001", validFrom, nil, 11, validFrom, nil))
	mock.ExpectExec(regexp.QuoteMeta(`UPDATE platform_user_vehicle_grant_history
SET valid_to=CASE WHEN valid_to IS NOT NULL AND valid_to<=NOW(3) THEN valid_to WHEN valid_from>NOW(3) THEN valid_from ELSE NOW(3) END,
revoked_by=?,updated_at=NOW(3) WHERE id=?`)).
		WithArgs("admin-a", uint64(11)).
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM platform_user_vehicle WHERE user_id=? AND vin NOT IN (?)`)).
		WithArgs(uint64(7), "VIN002").
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO platform_user_vehicle(user_id,vin,valid_from,valid_to,source_system,granted_by) VALUES(?,?,?,?,'manual',?)`)).
		WithArgs(uint64(7), "VIN002", sqlmock.AnyArg(), nil, "admin-a").
		WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO platform_user_vehicle_grant_history(user_id,vin,valid_from,valid_to,source_system,external_ref,granted_by)
VALUES(?,?,?,?, 'manual','',?)`)).
		WithArgs(uint64(7), "VIN002", sqlmock.AnyArg(), nil, "admin-a").
		WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectCommit()

	if err := replaceGrants(context.Background(), tx, 7, []string{"monitor"}, []vehicleGrantMutation{{VIN: "VIN002"}}, "admin-a", nil); err != nil {
		t.Fatalf("replaceGrants failed: %v", err)
	}
	if err := tx.Commit(); err != nil {
		t.Fatal(err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestReplaceGrantsPreservesUnchangedVehicleStartTime(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()

	mock.ExpectBegin()
	tx, err := db.Begin()
	if err != nil {
		t.Fatal(err)
	}
	mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM platform_user_menu WHERE user_id=?`)).
		WithArgs(uint64(7)).
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO platform_user_menu(user_id,menu_key,granted_by) VALUES(?,?,?)`)).
		WithArgs(uint64(7), "monitor", "admin-a").
		WillReturnResult(sqlmock.NewResult(1, 1))
	validFrom := time.Date(2026, 6, 5, 0, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*60*60))
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.vin,COALESCE(p.valid_from,p.granted_at),p.valid_to,
COALESCE(h.id,0),h.valid_from,h.valid_to
FROM platform_user_vehicle p
LEFT JOIN platform_user_vehicle_grant_history h
  ON h.id=(SELECT MAX(latest.id) FROM platform_user_vehicle_grant_history latest WHERE latest.user_id=p.user_id AND latest.vin=p.vin)
WHERE p.user_id=? FOR UPDATE`)).
		WithArgs(uint64(7)).
		WillReturnRows(sqlmock.NewRows([]string{"vin", "valid_from", "valid_to", "history_id", "history_valid_from", "history_valid_to"}).AddRow("VIN001", validFrom, nil, 12, validFrom, nil))
	mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM platform_user_vehicle WHERE user_id=? AND vin NOT IN (?)`)).
		WithArgs(uint64(7), "VIN001").
		WillReturnResult(sqlmock.NewResult(0, 0))
	mock.ExpectCommit()

	if err := replaceGrants(context.Background(), tx, 7, []string{"monitor"}, []vehicleGrantMutation{{VIN: "VIN001"}}, "admin-a", nil); err != nil {
		t.Fatalf("replaceGrants failed: %v", err)
	}
	if err := tx.Commit(); err != nil {
		t.Fatal(err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestReplaceGrantsUpdatesCurrentProjectionAndMatchingHistoryTogether(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	mock.ExpectBegin()
	tx, err := db.Begin()
	if err != nil {
		t.Fatal(err)
	}
	fromBefore := time.Date(2026, 7, 16, 14, 8, 18, 0, time.FixedZone("Asia/Shanghai", 8*60*60))
	fromAfter := time.Date(2026, 6, 5, 0, 0, 0, 0, time.FixedZone("Asia/Shanghai", 8*60*60))
	mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM platform_user_menu WHERE user_id=?`)).WithArgs(uint64(7)).WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO platform_user_menu(user_id,menu_key,granted_by) VALUES(?,?,?)`)).WithArgs(uint64(7), "statistics", "admin-a").WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.vin,COALESCE(p.valid_from,p.granted_at),p.valid_to,
COALESCE(h.id,0),h.valid_from,h.valid_to
FROM platform_user_vehicle p
LEFT JOIN platform_user_vehicle_grant_history h
  ON h.id=(SELECT MAX(latest.id) FROM platform_user_vehicle_grant_history latest WHERE latest.user_id=p.user_id AND latest.vin=p.vin)
WHERE p.user_id=? FOR UPDATE`)).WithArgs(uint64(7)).
		WillReturnRows(sqlmock.NewRows([]string{"vin", "valid_from", "valid_to", "history_id", "history_valid_from", "history_valid_to"}).AddRow("VIN001", fromBefore, nil, 19, fromAfter, nil))
	mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM platform_user_vehicle WHERE user_id=? AND vin NOT IN (?)`)).WithArgs(uint64(7), "VIN001").WillReturnResult(sqlmock.NewResult(0, 0))
	mock.ExpectExec(regexp.QuoteMeta(`UPDATE platform_user_vehicle SET valid_from=?,valid_to=?,source_system='manual',granted_by=? WHERE user_id=? AND vin=?`)).
		WithArgs(fromAfter, nil, "admin-a", uint64(7), "VIN001").WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec(regexp.QuoteMeta(`UPDATE platform_user_vehicle_grant_history
SET valid_from=?,valid_to=?,source_system='manual',granted_by=?,revoked_by='',updated_at=NOW(3) WHERE id=?`)).
		WithArgs(fromAfter, nil, "admin-a", uint64(19)).WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectCommit()
	changes := []vehicleGrantAuditChange{}
	if err := replaceGrants(context.Background(), tx, 7, []string{"statistics"}, []vehicleGrantMutation{{VIN: "VIN001", ValidFrom: &fromAfter}}, "admin-a", &changes); err != nil {
		t.Fatalf("replaceGrants failed: %v", err)
	}
	if err := tx.Commit(); err != nil {
		t.Fatal(err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
	if len(changes) != 1 || changes[0].Before == nil || changes[0].After == nil ||
		!changes[0].Before.ValidFrom.Equal(fromBefore) || !changes[0].After.ValidFrom.Equal(fromAfter) {
		t.Fatalf("grant audit must retain before/after interval: %+v", changes)
	}
}

func TestReplaceGrantsRepairsHistoryDriftEvenWhenProjectionDateIsUnchanged(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	mock.ExpectBegin()
	tx, err := db.Begin()
	if err != nil {
		t.Fatal(err)
	}
	projectionFrom := time.Date(2026, 7, 16, 14, 8, 18, 0, time.FixedZone("Asia/Shanghai", 8*60*60))
	driftedHistoryFrom := time.Date(2026, 6, 5, 19, 6, 0, 0, time.FixedZone("Asia/Shanghai", 8*60*60))
	mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM platform_user_menu WHERE user_id=?`)).WithArgs(uint64(7)).WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO platform_user_menu(user_id,menu_key,granted_by) VALUES(?,?,?)`)).WithArgs(uint64(7), "statistics", "admin-a").WillReturnResult(sqlmock.NewResult(1, 1))
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT p.vin,COALESCE(p.valid_from,p.granted_at),p.valid_to,
COALESCE(h.id,0),h.valid_from,h.valid_to
FROM platform_user_vehicle p
LEFT JOIN platform_user_vehicle_grant_history h
  ON h.id=(SELECT MAX(latest.id) FROM platform_user_vehicle_grant_history latest WHERE latest.user_id=p.user_id AND latest.vin=p.vin)
WHERE p.user_id=? FOR UPDATE`)).WithArgs(uint64(7)).
		WillReturnRows(sqlmock.NewRows([]string{"vin", "valid_from", "valid_to", "history_id", "history_valid_from", "history_valid_to"}).AddRow("VIN001", projectionFrom, nil, 19, driftedHistoryFrom, nil))
	mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM platform_user_vehicle WHERE user_id=? AND vin NOT IN (?)`)).WithArgs(uint64(7), "VIN001").WillReturnResult(sqlmock.NewResult(0, 0))
	mock.ExpectExec(regexp.QuoteMeta(`UPDATE platform_user_vehicle SET valid_from=?,valid_to=?,source_system='manual',granted_by=? WHERE user_id=? AND vin=?`)).
		WithArgs(projectionFrom, nil, "admin-a", uint64(7), "VIN001").WillReturnResult(sqlmock.NewResult(0, 0))
	mock.ExpectExec(regexp.QuoteMeta(`UPDATE platform_user_vehicle_grant_history
SET valid_from=?,valid_to=?,source_system='manual',granted_by=?,revoked_by='',updated_at=NOW(3) WHERE id=?`)).
		WithArgs(projectionFrom, nil, "admin-a", uint64(19)).WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectCommit()
	changes := []vehicleGrantAuditChange{}
	if err := replaceGrants(context.Background(), tx, 7, []string{"statistics"}, []vehicleGrantMutation{{VIN: "VIN001", ValidFrom: &projectionFrom}}, "admin-a", &changes); err != nil {
		t.Fatalf("replaceGrants failed: %v", err)
	}
	if err := tx.Commit(); err != nil {
		t.Fatal(err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
	if len(changes) != 1 {
		t.Fatalf("history drift repair must be audited: %+v", changes)
	}
}

func TestNormalizeVehicleGrantMutationsRequiresOrderedInterval(t *testing.T) {
	_, err := normalizeVehicleGrantMutations(userMutation{VehicleGrants: []userVehicleGrantInput{{
		VIN: "VIN001", ValidFrom: "2026-06-05T00:00", ValidTo: "2026-06-04T23:59",
	}}})
	if err == nil {
		t.Fatal("invalid authorization interval must be rejected")
	}
	grants, err := normalizeVehicleGrantMutations(userMutation{VehicleGrants: []userVehicleGrantInput{{
		VIN: "vin001", ValidFrom: "2026-06-05T00:00",
	}}})
	if err != nil || len(grants) != 1 || grants[0].VIN != "VIN001" || grants[0].ValidFrom == nil {
		t.Fatalf("valid authorization interval was not normalized: grants=%+v err=%v", grants, err)
	}
}
