// controllers/scoresController.js

const axios = require("axios");
const cache = require("../utils/cache");
const Score = require("../models/Score");
const logger = require("../utils/logger"); // Ensure logger is properly implemented

/**
 * Fetch live scores and team crest URLs from the external API.
 * @returns {Promise<Array>} Array of enriched match objects.
 */
const fetchScoresFromAPI = async () => {
  try {
    const matchesApiUrl = "https://api.football-data.org/v4/matches";
    const teamsApiUrl = "https://api.football-data.org/v4/teams"; // Endpoint to fetch team details

    // Step 1: Fetch Matches
    const matchesResponse = await axios.get(matchesApiUrl, {
      headers: {
        "X-Auth-Token": process.env.API_FOOTBALL_KEY, // Ensure this is set in your environment
      },
      params: {
        dateFrom: new Date(Date.now() - 24 * 60 * 60 * 1000) // Yesterday's date
          .toISOString()
          .split("T")[0],
        dateTo: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) // One week from now
          .toISOString()
          .split("T")[0],
      },
    });

    const matches = matchesResponse.data.matches;

    if (!matches || matches.length === 0) {
      logger.info("No matches found in the fetched data.");
      return [];
    }

    // Step 2: Extract Unique Team IDs
    const teamIds = new Set();
    matches.forEach((match) => {
      teamIds.add(match.homeTeam.id);
      teamIds.add(match.awayTeam.id);
    });

    // Step 3: Fetch Team Details in Bulk
    const teamDetails = {}; // Map of teamId -> crestUrl

    // Function to fetch details for a single team
    const fetchTeamDetails = async (teamId) => {
      try {
        const response = await axios.get(`${teamsApiUrl}/${teamId}`, {
          headers: {
            "X-Auth-Token": process.env.API_FOOTBALL_KEY,
          },
        });
        return { id: teamId, crest: response.data.crest };
      } catch (error) {
        logger.warn(
          `Failed to fetch team data for team ID ${teamId}: ${error.message}`
        );
        return { id: teamId, crest: "/default-crest.png" }; // Fallback to default crest
      }
    };

    // Limit concurrency to avoid hitting rate limits
    const CONCURRENT_REQUESTS = 10;
    const teamIdArray = Array.from(teamIds);
    for (let i = 0; i < teamIdArray.length; i += CONCURRENT_REQUESTS) {
      const batch = teamIdArray.slice(i, i + CONCURRENT_REQUESTS);
      const promises = batch.map((id) => fetchTeamDetails(id));
      const results = await Promise.all(promises);
      results.forEach((team) => {
        teamDetails[team.id] = team.crest;
      });
      // Optional: Introduce delay between batches if API rate limits are strict
      // await new Promise(resolve => setTimeout(resolve, 1000)); // 1 second delay
    }

    // Step 4: Map Competition IDs to League Names
    const leagueIds = {
      2014: "La Liga",
      2021: "Premier League",
      2002: "Bundesliga",
      2019: "Serie A",
      2015: "Ligue 1",
      2001: "UEFA Champions League",
      2146: "UEFA Europa League",
      // Add more mappings as needed
    };

    // Step 5: Enrich Match Data with Crest URLs
    const enrichedMatches = matches.map((match) => ({
      id: match.id,
      utcDate: match.utcDate,
      status: match.status,
      league: leagueIds[match.competition.id] || "Other",
      homeTeam: {
        id: match.homeTeam.id,
        name: match.homeTeam.name,
        shortName: match.homeTeam.shortName || match.homeTeam.name, // Use name if shortName is missing
        crest: teamDetails[match.homeTeam.id] || "/default-home-crest.png", // Fallback to default image
      },
      awayTeam: {
        id: match.awayTeam.id,
        name: match.awayTeam.name,
        shortName: match.awayTeam.shortName || match.awayTeam.name,
        crest: teamDetails[match.awayTeam.id] || "/default-away-crest.png",
      },
      score: match.score || {
        fullTime: { home: null, away: null },
        halfTime: { home: null, away: null },
      }, // Ensure score is always an object
      odds: Math.random().toFixed(2), // Placeholder for odds
      avgP: (Math.random() * 100).toFixed(2), // Placeholder for average probability
      avgG: (Math.random() * 5).toFixed(2), // Placeholder for average goals
      cG: Math.floor(Math.random() * 10), // Placeholder for custom metric
      BTS: Math.random() > 0.5 ? "Yes" : "No", // Both Teams to Score
      FTS: Math.random() > 0.5 ? "Yes" : "No", // First Team to Score
      cAG: Math.random() > 0.5 ? "Yes" : "No", // Custom metric
      fetchedAt: new Date(),
    }));

    return enrichedMatches;
  } catch (error) {
    logger.error("Error fetching scores from API:", error.message);
    throw new Error("Failed to fetch scores from external API.");
  }
};

/**
 * Store scores in the MongoDB database.
 * @param {Array} scores - Array of enriched match objects to store.
 */
const storeScoresInDB = async (scores) => {
  try {
    if (scores.length === 0) {
      logger.info("No scores to store in the database.");
      return;
    }

    // Use bulkWrite for upsert operations to handle inserts and updates
    const bulkOps = scores.map((score) => ({
      updateOne: {
        filter: { id: score.id }, // Use the unique 'id' field as the filter
        update: { $set: score },
        upsert: true, // Insert if not exists
      },
    }));

    const result = await Score.bulkWrite(bulkOps, { ordered: false });
    logger.info(`Bulk write result: ${JSON.stringify(result)}`);
  } catch (err) {
    if (err.code === 11000) {
      logger.warn("Duplicate score entries detected. Skipping duplicates.");
    } else {
      logger.error("Error inserting scores into DB:", err.message);
      throw new Error("Failed to store scores in the database.");
    }
  }
};

/**
 * Fetch scores from the database.
 * @returns {Promise<Array>} Array of score documents.
 */
const getScoresFromDB = async () => {
  try {
    const scores = await Score.find({})
      .sort({ utcDate: -1 }) // Sort by latest date
      .limit(100) // Limit to latest 100 matches
      .lean(); // Returns plain JavaScript objects
    return scores;
  } catch (error) {
    logger.error("Error fetching scores from DB:", error.message);
    throw new Error("Failed to retrieve scores from the database.");
  }
};

/**
 * Group matches by league.
 * @param {Array} matches - Array of match objects.
 * @returns {Object} Grouped matches by league.
 */
const groupMatchesByLeague = (matches) => {
  return matches.reduce((acc, match) => {
    if (!acc[match.league]) {
      acc[match.league] = [];
    }
    acc[match.league].push(match);
    return acc;
  }, {});
};

/**
 * Prepare live scores by fetching from DB or external API and caching the result.
 * @returns {Promise<Object>} Grouped matches by league.
 */
const prepareLiveScores = async () => {
  // Check if live scores are cached
  const cachedScores = cache.get("liveScores");
  if (cachedScores) {
    logger.info("Serving cached live scores.");
    return cachedScores;
  }

  // Fetch scores from the database
  let scores = await getScoresFromDB();

  // If no scores in DB, fetch from external API and store
  if (!scores || scores.length === 0) {
    logger.info("No scores found in DB. Fetching from external API.");
    scores = await fetchScoresFromAPI();

    if (scores.length === 0) {
      // No matches fetched; return empty object
      logger.info("No matches found after fetching from API.");
      return {};
    }

    // Store fetched scores in the database
    await storeScoresInDB(scores);

    // Fetch again from DB after inserting
    scores = await getScoresFromDB();
  }

  // Group scores by league
  const groupedMatches = groupMatchesByLeague(scores);

  // Cache the grouped matches with TTL of 5 minutes (300 seconds)
  cache.set("liveScores", groupedMatches, 300);
  logger.info("Cached live scores.");

  return groupedMatches;
};

/**
 * Controller to handle fetching and serving live scores.
 * Endpoint: GET /api/scores
 */
const scoresController = async (req, res) => {
  try {
    const groupedMatches = await prepareLiveScores();
    res.status(200).json(groupedMatches);
  } catch (error) {
    logger.error("Error in scoresController:", error.message);
    res.status(500).json({
      message: "Unable to retrieve live scores. Please try again later.",
      error: error.message,
    });
  }
};

module.exports = {
  scoresController,
  prepareLiveScores, // Exported for use in cron jobs or other parts of the application
};
// models/Score.js

const mongoose = require("mongoose");

const TeamSchema = new mongoose.Schema({
  id: {
    type: Number,
    required: true,
  },
  name: {
    type: String,
    required: true,
  },
  shortName: {
    type: String,
  },
  crest: {
    type: String, // URL to the team's crest image
  },
});

const ScoreSchema = new mongoose.Schema(
  {
    id: {
      type: Number,
      unique: true,
      required: true,
    },
    utcDate: {
      type: Date,
      required: true,
    },
    status: {
      type: String,
      required: true,
      enum: [
        "SCHEDULED",
        "LIVE",
        "IN_PLAY",
        "PAUSED",
        "FINISHED",
        "POSTPONED",
        "SUSPENDED",
        "CANCELED",
      ],
    },
    league: {
      type: String,
      required: true,
    },
    homeTeam: {
      type: TeamSchema,
      required: true,
    },
    awayTeam: {
      type: TeamSchema,
      required: true,
    },
    score: {
      fullTime: {
        home: { type: Number, default: null },
        away: { type: Number, default: null },
      },
      halfTime: {
        home: { type: Number, default: null },
        away: { type: Number, default: null },
      },
      extraTime: {
        home: { type: Number, default: null },
        away: { type: Number, default: null },
      },
      penalties: {
        home: { type: Number, default: null },
        away: { type: Number, default: null },
      },
    },
    odds: {
      type: String,
    },
    avgP: {
      type: String, // Percentage or other format as string
    },
    avgG: {
      type: String, // Goals or other metric as string
    },
    cG: {
      type: Number, // Custom metric
    },
    BTS: {
      type: String, // "Yes" or "No"
      enum: ["Yes", "No"],
    },
    FTS: {
      type: String, // "Yes" or "No"
      enum: ["Yes", "No"],
    },
    cAG: {
      type: String, // "Yes" or "No"
      enum: ["Yes", "No"],
      default: "No",
    },
    fetchedAt: {
      type: Date,
      default: Date.now,
    },
  },
  {
    timestamps: true, // Adds createdAt and updatedAt fields
  }
);

module.exports = mongoose.model("Score", ScoreSchema);
const express = require("express");
const router = express.Router();
const scoresController = require("../controllers/scoresController");


router.get("/", scoresController);

module.exports = router;
const cron = require("node-cron");
const axios = require("axios");

// Function to fetch live scores
const fetchLiveScores = async () => {
  try {
    const response = await axios.get(
      "https://api.football-data.org/v4/matches",
      {
        headers: { "X-Auth-Token": process.env.API_FOOTBALL_KEY },
      }
    );
    console.log("Live scores fetched:", response.data);
  } catch (error) {
    console.error("Error fetching live scores:", error);
  }
};

// Function to start the cron job (run every 1 minutes)
const start = () => {
  cron.schedule("*/5 * * * *", async () => {
    console.log("Fetching live scores...");
    await fetchLiveScores();
  });
  console.log("Cron job started: Fetching live scores every 2 minutes");
};

module.exports = { start };
// taskQueueWorker.js
const { fetchLiveScores } = require("./services/apiService");
const { start } = require("./taskQueue/matchUpdateQueue");
// Initialize cron job worker to start fetching live scores every 5 minutes
start();

// You can add other workers or background tasks here as needed
// services/apiService.js
const axios = require("axios");
const { footballApiUrl, apiKey } = require("../apiConfig");

// Fetch live scores
const fetchLiveScores = async () => {
  try {
    const response = await axios.get(`${footballApiUrl}/matches`, {
      headers: { "X-Auth-Token": apiKey },
    });
    return response.data;
  } catch (error) {
    console.error("Error fetching live scores:", error);
    throw new Error("Could not fetch live scores");
  }
};

module.exports = { fetchLiveScores };
// utils/cache.js

const NodeCache = require("node-cache");

// Initialize cache with a default TTL of 5 minutes (300 seconds)
const cache = new NodeCache({ stdTTL: 300, checkperiod: 320 });

// Optional: Listen for expired keys (useful for logging or debugging)
cache.on("expired", (key, value) => {
  console.log(`Cache expired for key: ${key}`);
});

module.exports = cache;
// utils/cacheHelper.js

const cache = require("./cache");

/**
 * Retrieves data from the cache or fetches it using the provided fetch function.
 *
 * @param {string} key - The cache key.
 * @param {Function} fetchFn - The function to fetch data if not in cache.
 * @returns {Promise<any>} - The cached or fetched data.
 */
async function getCachedData(key, fetchFn) {
  const cachedData = cache.get(key);
  if (cachedData) {
    console.log(`Cache hit for key: ${key}`);
    return cachedData;
  }

  console.log(`Cache miss for key: ${key}. Fetching data...`);
  const data = await fetchFn();
  cache.set(key, data);
  console.log(`Data cached for key: ${key}`);
  return data;
}

module.exports = { getCachedData };
// utils/logger.js

const { createLogger, format, transports } = require("winston");

// Define the custom settings for each transport
const options = {
  file: {
    level: "info",
    filename: `logs/app.log`,
    handleExceptions: true,
    format: format.combine(format.timestamp(), format.json()),
    maxsize: 5242880, // 5MB
    maxFiles: 5,
  },
  console: {
    level: "debug",
    handleExceptions: true,
    format: format.combine(format.colorize(), format.simple()),
  },
};

// Instantiate a new Winston Logger with the settings defined above
const logger = createLogger({
  transports: [
    new transports.File(options.file),
    new transports.Console(options.console),
  ],
  exitOnError: false, // Do not exit on handled exceptions
});

// Create a stream object with a 'write' function that will be used by morgan
logger.stream = {
  write: function (message, encoding) {
    logger.info(message.trim());
  },
};

module.exports = logger;
require("dotenv").config();
const express = require("express");
const mongoose = require("mongoose");
const newsRoutes = require("./routes/newsRoutes");
const scoreRoutes = require("./routes/scoreRoutes");
const taskQueue = require("./taskQueue/matchUpdateQueue");
const standingsRoutes = require("./routes/standingsRoutes");
const fixturesRouter = require("./routes/fixturesRouter");
const leagueRoutes = require("./routes/leagueRoutes"); // New
const leaguesRoutes = require("./routes/leaguesRoutes"); // New
const articleRoutes = require("./routes/articleRoutes");
const playerSearchRoutes = require("./routes/playerSearchRoutes");
const videoRoutes = require("./routes/videoRoutes");
const cors = require("cors");
const path = require("path");
const app = express();

// CORS configuration
app.use(
  cors({
    origin: "http://localhost:3000", // Replace with your frontend URL
    methods: ["GET", "POST", "PUT", "DELETE"],
    allowedHeaders: ["Content-Type", "Authorization"],
  })
);

const PORT = process.env.PORT || 5000;

// Connect to MongoDB
mongoose
  .connect(process.env.MONGO_URL, {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  })
  .then(() => console.log("Connected to MongoDB"))
  .catch((err) => console.error("MongoDB connection error:", err));

// Middleware
app.use(express.json());

// Routes
app.use("/assets", express.static(path.join(__dirname, "assets")));
app.use("/uploads", express.static(path.join(__dirname, "uploads")));
app.use("/api/players", playerSearchRoutes);
app.use("/api/news", newsRoutes);
app.use("/api/scores", scoreRoutes);
app.use("/api/articles", articleRoutes);
app.use("/api", standingsRoutes);
app.use("/api", fixturesRouter);
app.use("/api/leagues", leaguesRoutes);
app.use("/api/league", leagueRoutes);
app.use("/api/videos", videoRoutes);

// Error handling middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send("Something broke!");
});

// Test route
app.get("/api/test", (req, res) => {
  res.json({ message: "Test route working" });
});

// Start server
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

// Start task queue worker (for fetching live scores)
taskQueue.start();

module.exports = app; // Export for testing purposes

error: uncaughtException: Route.get() requires a callback function but got a [object Object]
Error: Route.get() requires a callback function but got a [object Object]
    at Route.<computed> [as get] (C:\Node js programs\Football App\football-app-backend\node_modules\express\lib\router\route.js:216:15)
    at proto.<computed> [as get] (C:\Node js programs\Football App\football-app-backend\node_modules\express\lib\router\index.js:521:19)
    at Object.<anonymous> (C:\Node js programs\Football App\football-app-backend\routes\scoreRoutes.js:6:8) 
    at Module._compile (node:internal/modules/cjs/loader:1358:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)
    at Module.load (node:internal/modules/cjs/loader:1208:32)
    at Module._load (node:internal/modules/cjs/loader:1024:12)
    at Module.require (node:internal/modules/cjs/loader:1233:19)
    at require (node:internal/modules/helpers:179:18)
    at Object.<anonymous> (C:\Node js programs\Football App\football-app-backend\server.js:5:21) {"date":"Thu Oct 03 2024 00:01:50 GMT+0530 (India Standard Time)","error":{},"exception":true,"os":{"loadavg":[0,0,0],"uptime":89105.281},"process":{"argv":["C:\\Program Files\\nodejs\\node.exe","C:\\Node js programs\\Football App\\football-app-backend\\server"],"cwd":"C:\\Node js programs\\Football App\\football-app-backend","execPath":"C:\\Program Files\\nodejs\\node.exe","gid":null,"memoryUsage":{"arrayBuffers":18318143,"external":21472258,"heapTotal":36274176,"heapUsed":23779816,"rss":76189696},"pid":30660,"uid":null,"version":"v20.13.1"},"stack":"Error: Route.get() requires a callback function but got a [object Object]\n    at Route.<computed> [as get] (C:\\Node js programs\\Football App\\football-app-backend\\node_modules\\express\\lib\\router\\route.js:216:15)\n    at proto.<computed> [as get] (C:\\Node js programs\\Football App\\football-app-backend\\node_modules\\express\\lib\\router\\index.js:521:19)\n    at Object.<anonymous> (C:\\Node js programs\\Football App\\football-app-backend\\routes\\scoreRoutes.js:6:8)\n    at Module._compile (node:internal/modules/cjs/loader:1358:14)\n    at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)\n    at Module.load (node:internal/modules/cjs/loader:1208:32)\n    at Module._load (node:internal/modules/cjs/loader:1024:12)\n    at Module.require (node:internal/modules/cjs/loader:1233:19)\n    at require (node:internal/modules/helpers:179:18)\n    at Object.<anonymous> (C:\\Node js programs\\Football App\\football-app-backend\\server.js:5:21)","trace":[{"column":15,"file":"C:\\Node js programs\\Football App\\football-app-backend\\node_modules\\express\\lib\\router\\route.js","function":"Route.<computed> [as get]","line":216,"method":"<computed> [as get]","native":false},{"column":19,"file":"C:\\Node js programs\\Football App\\football-app-backend\\node_modules\\express\\lib\\router\\index.js","function":"proto.<computed> [as get]","line":521,"method":"<computed> [as get]","native":false},{"column":8,"file":"C:\\Node js programs\\Football App\\football-app-backend\\routes\\scoreRoutes.js","function":null,"line":6,"method":null,"native":false},{"column":14,"file":"node:internal/modules/cjs/loader","function":"Module._compile","line":1358,"method":"_compile","native":false},{"column":10,"file":"node:internal/modules/cjs/loader","function":"Module._extensions..js","line":1416,"method":".js","native":false},{"column":32,"file":"node:internal/modules/cjs/loader","function":"Module.load","line":1208,"method":"load","native":false},{"column":12,"file":"node:internal/modules/cjs/loader","function":"Module._load","line":1024,"method":"_load","native":false},{"column":19,"file":"node:internal/modules/cjs/loader","function":"Module.require","line":1233,"method":"require","native":false},{"column":18,"file":"node:internal/modules/helpers","function":"require","line":179,"method":null,"native":false},{"column":21,"file":"C:\\Node js programs\\Football App\\football-app-backend\\server.js","function":null,"line":5,"method":null,"native":false}]}
This is the error I'm getting can you remove it 
I want to store score to my database that's why this error is coming