import { verifyLogtoToken, xcxApi } from "./utils/server";
import ics from 'ics';
import index from "./index.html";
import { getUidScore } from "./services/uidScoreStore";
import { checkIsUserFav, favPlayer, listFavPlayers, unFavPlayer } from "./services/favPlayerService";
import { BattleService } from "./services/BattleService";
import { KaiqiuService } from "./services/KaiqiuService";
import dayjs from "dayjs";
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import type { IEventInfo, WsPaylaod } from "./types";
import { EventSubscribeService } from "./services/EventSubscribeService";
import { WebSocketService } from "./services/WebsocketService";
import type { JWTPayload } from "jose";
import { prisma } from "./prisma/db";
import xprisma from "./dao/xprisma";
import { EventWatchSchedule } from "./schedules/EventWatchSchedule";
import { sendNotification } from "./utils/firebase-admin";

dayjs.extend(utc);
dayjs.extend(timezone);

const server = Bun.serve({
  idleTimeout: 30,
  port: process.env.PORT || 3000,
  routes: {
    // Serve index.html for all unmatched routes.
    "/*": index,
    '/assets/*': async (req) => {
      const pathname = new URL(req.url).pathname;
      const filepath = `.${pathname}`;
      // console.log('Read file: %s', filepath);
      if (!/^\/assets/.test(pathname)) return new Response(null, { status: 404 });
      const file = await Bun.file(filepath);
      if (!await file.exists()) {
        return new Response(null, { status: 404 });
      }
      return new Response(file);
    },
    "/sw.js": async () => {
      const build = await Bun.build({
        entrypoints: ["./src/sw.ts"],
        target: "browser",
        // 如果你需要压缩，可以开启
        minify: process.env.NODE_ENV === "production",
      });

      if (!build.success) {
        return new Response("Build Error", { status: 500 });
      }

      // 读取编译后的第一个输出文件（即 sw.js）
      const blob = build.outputs[0];
      
      return new Response(blob, {
        headers: {
          "Content-Type": "application/javascript",
          // 开发环境下禁用缓存，确保 SW 能及时更新
          "Cache-Control": "no-cache", 
          "Service-Worker-Allowed": "/",
        },
      });
    },
    "/api/notification-token": {
      async GET(req) {
        const { sub } = await verifyLogtoToken(req.headers) ?? {};
        const token = new URL(req.url).searchParams.get('token');
        if (!sub || !token) return Response.json({
          isSubscribed: false,
        });
        const count = await prisma.notificationToken.count({
          where: { logto_uid: sub, token },
        });
        return Response.json({
          isSubscribed: count > 0,
        });
      },
      async DELETE(req) {
        const { sub } = await verifyLogtoToken(req.headers) ?? {};
        const { token } = await req.json();
        if (!sub || !token) return Response.json({ success: true });
        await prisma.notificationToken.deleteMany({ where: { logto_uid: sub, token } });
        return Response.json({ success: true });
      },
      async PUT(req) {
        const { sub } = await verifyLogtoToken(req.headers) ?? {};
        const { token } = await req.json();
        if (!sub || !token) return Response.json({
          success: false,
        });
        const where = { logto_uid: sub, token };
        const [hasOldToken] = await Promise.all([
          prisma.notificationToken.count({ where }).then(num => num > 0),
        ]);
        await sendNotification(token, { title: '通知已注册!' });
        if (hasOldToken) {
          return Response.json({
            success: true,
            message: 'token not change',
          });
        } else {
          await prisma.notificationToken.create({ data: { logto_uid: sub, token } });
        }
        return Response.json({ success: true, hasOldToken });
      }
    },
    "/api/club/find": {
      async GET(req) {
        const searchParams = new URL(req.url).searchParams;
        const key = searchParams.get('key') ?? '';
        const normalClub = searchParams.get('normalClub');
        const page = Number(searchParams.get('page'));
        const data = await KaiqiuService.findClub(key, page, Boolean(normalClub));
        return Response.json(data);
      }
    },
    "/api/club/:id": {
      async GET(req) {
        const id = req.params.id;
        const data = await KaiqiuService.getClubInfo(id);
        return Response.json(data);
      }
    },
    "/api/club/:clubid/events": {
      async GET(req) {
        const searchParams = new URL(req.url).searchParams;
        const page = Number(searchParams.get('page')) ?? 1;
        const force = Boolean(searchParams.get('force')) ?? false;
        const data = await KaiqiuService.listClubEvents(req.params.clubid, page, force);
        return Response.json(data);
      }
    },
    "/api/club/:id/calendar.ics": {
      async GET(req) {
        const id = req.params.id;
        const clubInfo = await KaiqiuService.getClubInfo(id);
        let allEvents: IEventInfo[] = [];
        let page = 1;
        let events = await KaiqiuService.listClubEvents(id, page, true);
        allEvents = allEvents.concat(...events.data);
        while (events.data.every(e => !e.isFinished)) {
          page += 1;
          events = await KaiqiuService.listClubEvents(id, page, true);
          allEvents = allEvents.concat(...events.data);
        }
        const configs: ics.EventAttributes[] = allEvents.filter(e => !e.isFinished).map(e => ({
          ...(clubInfo?.geo ? { geo: {
            lat: clubInfo.geo.lat,
            lon: clubInfo.geo.lng,
          } } : {}),
          start: ics.convertTimestampToArray(dayjs.tz(e.startDate, 'Asia/Tokyo').toDate().getTime(), 'local') ,
          duration: { hours: 6, minutes: 30 },
          title: e.title.replace(clubInfo?.name || '', ''),
          // end: dayjs(event.startDate).add(6, 'h').add(30, 'minute').format('YYYY-MM-DD HH:mm'),
          // description: 'Annual 10-kilometer run in Boulder, Colorado',
          url: `https://tt.ksr.la/event/${e.matchId}`,
          ...(e.location ? { location: e.location } : {}),
          // geo: { lat: events.location.lat, lon: events.location.lng },
          alarms: [
            { action: 'display', summary: e.title, description: '距离比赛开始还有 2 小时', trigger: { before: true, hours: 2 } },
            { action: 'audio', trigger: { before: true, hours: 2 } },
          ],
          // categories: ['10k races', 'Memorial Day Weekend', 'Boulder CO'],
          // status: 'CONFIRMED',
          // busyStatus: 'BUSY',
          // organizer: { name: 'Admin', email: 'Race@BolderBOULDER.com' },
          // attendees: [
          //   { name: 'Adam Gibbons', email: 'adam@example.com', rsvp: true, partstat: 'ACCEPTED', role: 'REQ-PARTICIPANT' },
          //   { name: 'Brittany Seaton', email: 'brittany@example2.org', dir: 'https://linkedin.com/in/brittanyseaton', role: 'OPT-PARTICIPANT' }
          // ]
        }));
        const data: string = await new Promise(resolve => ics.createEvents(configs, {
          calName: clubInfo?.name ? `${clubInfo.name}` : '',
        }, (err, data) => {
          if (err) {
            console.log(err);
            resolve('');
          }
          resolve(data);
        }));
        return new Response(data, { headers: {
          'Content-Type': 'text/calendar; charset=utf-8',
        } });
      }
    },
    "/api/subscribe-event": {
      async GET(req) {
        const { sub = '' } = await verifyLogtoToken(req.headers);
        if (!sub) return Response.json([]);
        const events = await EventSubscribeService.getEvents(sub);
        return Response.json(events);
      }
    },
    "/api/subscribe-event/:matchId": {
      async GET(req) {
        const id = req.params.matchId; // 获取比赛ID
        const { sub = '' } = await verifyLogtoToken(req.headers);
        if (!sub) return Response.json({ ok: false, message: 'Not login.' });
        return Response.json({
          isSub: await EventSubscribeService.isSub(sub, id),
        });
      },
      async PUT(req) {
        const id = req.params.matchId; // 获取比赛ID
        const { sub = '' } = await verifyLogtoToken(req.headers);
        if (!sub) return Response.json({ ok: false, message: 'Not login.' });
        return Response.json({
          ok: await EventSubscribeService.sub(sub, id),
        });
      },
      async DELETE(req) {
        const id = req.params.matchId; // 获取比赛ID
        const { sub = '' } = await verifyLogtoToken(req.headers);
        if (!sub) return Response.json({ ok: false, message: 'Not login.' });
        return Response.json({
          ok: await EventSubscribeService.unSub(sub, id),
        });
      },
    },
    "/api/match/:matchId": {
      async GET(req) {
        const data = await KaiqiuService.getMatchDetail(req.params.matchId);
        return Response.json(data);
      }
    },
    "/api/match/:matchId/:itemId": {
      async GET(req) {
        const { matchId, itemId } = req.params;
        const data = await xcxApi.getMemberDetail(matchId, itemId);
        return Response.json(data);
      }
    },
    "/api/user/find": {
      async GET(req) {
        const searchParams = new URL(req.url).searchParams;
        const key = searchParams.get('key') ?? '';
        const page = Number(searchParams.get('page'));
        const users = await xcxApi.findUser(key, page);
        return Response.json(users);
      }
    },
    "/api/user/nowScores": {
      async POST(req) {
        const { uids } = await req.json();
        const uidScore = await getUidScore(uids);
        return Response.json(uidScore);
      }
    },
    "/api/user/:uid": {
      async GET(req) {
        const uid = req.params.uid;
        if (uid === '0') return Response.json(null);
        const profile = await xcxApi.getAdvProfile(uid);
        return Response.json(profile);
      },
    },
    "/api/user/:uid/games": {
      async GET(req) {
        const uid = req.params.uid;
        if (uid === '0') return Response.json([]);
        const search = new URL(req.url).searchParams;
        const page = Number(search.get('page')) ?? 0;
        const resp = await xcxApi.getGameList(uid, page);
        return Response.json(resp);
      },
    },
    "/api/user/:uid/tags": {
      async GET(req) {
        const uid = req.params.uid;
        if (uid == '0') return Response.json([]);
        const profile = await xcxApi.getPlayerTags(uid);
        return Response.json(profile);
      },
    },
    "/api/fav": {
      async GET(req) {
        const payload = await verifyLogtoToken(req.headers);
        const list = await listFavPlayers(`${payload.sub}`);
        return Response.json(list.filter(Boolean));
      }
    },
    "/api/fav/:uid": {
      async GET(req) {
        const payload = await verifyLogtoToken(req.headers);
        const isFav = await checkIsUserFav(`${payload.sub}`, req.params.uid);
        return Response.json({ isFav });
      },
      async PUT(req) {
        const payload = await verifyLogtoToken(req.headers);
        await favPlayer(`${payload.sub}`, req.params.uid);
        return Response.json({ ok: 'ok' });
      },
      async DELETE(req) {
        const payload = await verifyLogtoToken(req.headers);
        await unFavPlayer(`${payload.sub}`, req.params.uid);
        return Response.json({ ok: 'ok' , uid: req.params.uid });
      }
    },
    '/api/battle/:eventId': {
      async PUT(req) {
        const { data } = await req.json();
        const eventId = req.params.eventId;
        const code = await BattleService.publishBattle(eventId, data);
        return Response.json({ code });
      },
      async GET(req) {
        const code = new URL(req.url).searchParams.get('code');
        const eventId = req.params.eventId;
        if (!code) return Response.json({});
        const data = await BattleService.getBattle(eventId, code);
        return Response.json(data);
      }
    },
    '/api/account/location': {
      async GET(req) {
        const { lat = 0, lng = 0 } = await req.json();
        const locations = await xprisma.userLocation.findNearby({ lng, lat }, 500000);
        return Response.json(locations[0]);
      },
      async PUT(req) {
        const { sub } = await verifyLogtoToken(req.headers);
        const { id, lat, lng, ...data } = await req.json();
        const record = await prisma.userLocation.update({
          where: { id, logto_uid: sub },
          data: data,
        }).catch(() => null);
        if (record !== null && lat && lng) {
          await xprisma.userLocation.updateLocation(record.id, { lat, lng });
        }
        return Response.json([]);
      },
      async POST(req) {
        const { sub } = await verifyLogtoToken(req.headers);
        const { lat, lng, name } = await req.json();
        if (!sub || !lat || !lng || !name) {
          return Response.json({
            success: false,
            message: '参数错误',
          });
        }
        const kaiqiu_uid = await prisma.userBind.findFirst({
          where: { logto_uid: sub },
          select: { logto_uid: true }
        }).then((r) => r?.logto_uid) ?? '';
        const response = await xprisma.userLocation.createCustom(sub, name, { lat, lng }, kaiqiu_uid)
        .then(() => ({
          success: true,
          message: '创建成功',
        }))
        .catch(() => ({
          success: false,
          message: '创建失败',
        }));
        return Response.json(response);
      },
      async DELETE(req) {
        const { sub } = await verifyLogtoToken(req.headers);
        const { id } = await req.json();
        await prisma.userLocation.delete({ where: { id, logto_uid: sub }});
        return Response.json({ success: true });
      },
    },
    '/api/account/bind': {
      async GET(req) {
        const { sub } = await verifyLogtoToken(req.headers);
        if (!sub) return Response.json(false);
        const record = await prisma.userBind.findFirst({
          where: { logto_uid: sub },
        });
        return Response.json({
          isBinded: record !== null,
          uid: record?.kaiqiu_uid,
        });
      },
      async PUT(req) {
        const { sub } = await verifyLogtoToken(req.headers);
        if (!sub) return Response.json({ message: 'Not login' }, { status: 401 });
        const { username, password } = await req.json();
        const result = await KaiqiuService.login(username, password);
        if (!result.success || !result.uid) return Response.json({
          message: '账号或密码错误',
          success: false,
        });
        const { uid } = result;
        await prisma.userBind.create({
          data: { logto_uid: sub, kaiqiu_uid: uid },
        }).catch();
        return Response.json({
          message: '绑定成功',
          success: true,
        });
      }
    },
    '/ws': {
      async GET(req, server) {
        const token = new URL(req.url).searchParams.get('token');
        if (!token) return new Response('Not valid token', { status: 401 });
        const user = await verifyLogtoToken(token) as Required<JWTPayload>;
        if (!user.sub) return new Response('Not valid token', { status: 401 });
        console.debug('ws connect', user.sub);
        server.upgrade(req, {
          data: { user },
        });
      }
    },
  },
  websocket: {
    data: {} as WsPaylaod,
    open(ws) {
      WebSocketService.addConnection(ws);
    },
    message(ws, message) {
      try {
        WebSocketService.processMessage(ws, message);
      } catch(e) {
        console.debug('Parse message error', e, message.toString());
      }
    },
    close(ws, code, reason) {
      console.debug('close ws', code, reason)
      WebSocketService.removeConnection(ws);
    },
  },

  development: process.env.NODE_ENV !== "production" && {
    // Enable browser hot reloading in development
    hmr: true,

    // Echo console logs from the browser to the server
    console: true,
  },
});

const eventSchedule = new EventWatchSchedule();
eventSchedule.start();

console.log(`🚀 Server running at ${server.url}`);
