import {
  Controller,
  Post,
  Delete,
  Route,
  Security,
  Tags,
  Body,
  Path,
  Request,
  Response,
  Get,
} from "tsoa";
import { AppDataSource } from "../database/data-source";
import HttpSuccess from "../interfaces/http-success";
import HttpStatusCode from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
import { CreateApiKey, ApiKey } from "../entities/ApiKey";
import { In } from "typeorm";
import { RequestWithUser } from "../middlewares/user";
import { ApiName } from "../entities/ApiName";
import { ApiHistory } from "../entities/ApiHistory";
import { OrgRoot } from "../entities/OrgRoot";
import { OrgChild1 } from "../entities/OrgChild1";
import { OrgChild2 } from "../entities/OrgChild2";
import { OrgChild3 } from "../entities/OrgChild3";
import { OrgChild4 } from "../entities/OrgChild4";
import { OrgRevision } from "../entities/OrgRevision";

const jwt = require("jsonwebtoken");
@Route("api/v1/org/apiKey")
@Tags("ApiKey")
@Security("bearerAuth")
@Response(
  HttpStatusCode.INTERNAL_SERVER_ERROR,
  "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง",
)
export class ApiKeyController extends Controller {
  private apiKeyRepository = AppDataSource.getRepository(ApiKey);
  private apiNameRepository = AppDataSource.getRepository(ApiName);
  private apiHistoryRepository = AppDataSource.getRepository(ApiHistory);
  private orgRootRepository = AppDataSource.getRepository(OrgRoot);
  private orgChild1Repository = AppDataSource.getRepository(OrgChild1);
  private orgChild2Repository = AppDataSource.getRepository(OrgChild2);
  private orgChild3Repository = AppDataSource.getRepository(OrgChild3);
  private orgChild4Repository = AppDataSource.getRepository(OrgChild4);
  private orgRevisionRepository = AppDataSource.getRepository(OrgRevision);

  /**
   * API ตรวจสอบและถอดรหัส JWT token
   *
   * @summary ตรวจสอบ JWT API Key
   */
  @Post("verify")
  async verifyApiKey(@Body() requestBody: { token: string }) {
    try {
      const jwtSecret = process.env.JWT_SECRET || "your-default-secret-key";
      console.log("JWT_SECRET from env:", process.env.JWT_SECRET ? "exists" : "not found");
      console.log("Using secret:", jwtSecret);

      const decoded = jwt.verify(requestBody.token, jwtSecret);
      return new HttpSuccess({
        valid: true,
        data: decoded,
      });
    } catch (error: any) {
      console.error("JWT Verification Error:", error.message);
      return new HttpSuccess({
        valid: false,
        error: error.message,
      });
    }
  }

  /**
   * API สร้าง Api Key
   *
   * @summary สร้าง Api Key (ADMIN)
   *
   */
  @Post()
  async createApiKey(
    @Body()
    requestBody: CreateApiKey,
    @Request() request: RequestWithUser,
  ) {
    const checkName = await this.apiKeyRepository.findOne({
      where: { name: requestBody.name },
    });
    if (checkName) throw new HttpError(HttpStatusCode.NOT_FOUND, "ชื่อนี้มีอยู่ในระบบแล้ว");

    const apiName = await this.apiNameRepository.find({
      where: { id: In(requestBody.apiId) },
    });

    const apiKey = Object.assign(new ApiKey(), requestBody);

    // Create JWT token with embedded data
    const tokenPayload = {
      keyId: apiKey.id || require("crypto").randomUUID(),
      name: apiKey.name,
      accessType: apiKey.accessType,
      dnaRootId: apiKey.dnaRootId,
      dnaChild1Id: apiKey.dnaChild1Id,
      dnaChild2Id: apiKey.dnaChild2Id,
      dnaChild3Id: apiKey.dnaChild3Id,
      dnaChild4Id: apiKey.dnaChild4Id,
      apiIds: requestBody.apiId,
      createdBy: request.user.sub,
      createdAt: new Date().toISOString(),
      iat: Math.floor(Date.now() / 1000),
    };

    // Sign JWT with secret (you should use environment variable for the secret)
    const jwtSecret = process.env.JWT_SECRET || "your-default-secret-key";

    const jwtToken = jwt.sign(tokenPayload, jwtSecret, {
      expiresIn: "365d", // 1 year expiration
    });

    apiKey.keyApi = jwtToken;
    apiKey.apiNames = apiName;
    apiKey.createdUserId = request.user.sub;
    apiKey.createdFullName = request.user.name;
    apiKey.lastUpdateUserId = request.user.sub;
    apiKey.lastUpdateFullName = request.user.name;
    apiKey.createdAt = new Date();
    apiKey.lastUpdatedAt = new Date();
    await this.apiKeyRepository.save(apiKey);
    return new HttpSuccess(apiKey.keyApi);
  }

  /**
   * API ลบ Api Key
   *
   * @summary ลบ Api Key (ADMIN)
   *
   * @param {string} id  Id  Api Key
   */
  @Delete("{id}")
  async deleteApiKey(@Path() id: string, @Request() req: RequestWithUser) {
    const delApiKey = await this.apiKeyRepository.findOne({
      where: { id },
      relations: ["apiHistorys"],
    });
    if (!delApiKey) {
      throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลตำแหน่งนี้");
    }
    await this.apiHistoryRepository.remove(delApiKey.apiHistorys);
    await this.apiKeyRepository.remove(delApiKey);
    return new HttpSuccess();
  }

  /**
   * API รายการ Api Key
   *
   * @summary รายการ Api Key (ADMIN)
   *
   */
  @Get("list")
  async listApiKey() {
    const apiKey = await this.apiKeyRepository.find({
      relations: ["apiNames", "apiHistorys"],
      order: { createdAt: "DESC", apiNames: { createdAt: "DESC" } },
    });

    const orgNames = await this.buildOrgNameBatch(apiKey);

    const data = apiKey.map((_data) => ({
      id: _data.id,
      createdAt: _data.createdAt,
      createdUserId: _data.createdUserId,
      createdFullName: _data.createdFullName,
      name: _data.name,
      accessType: _data.accessType,
      dnaRootId: _data.dnaRootId,
      dnaChild1Id: _data.dnaChild1Id,
      dnaChild2Id: _data.dnaChild2Id,
      dnaChild3Id: _data.dnaChild3Id,
      dnaChild4Id: _data.dnaChild4Id,
      orgName: orgNames.get(_data.id),
      apiNames: _data.apiNames.map((x) => ({
        id: x.id,
        name: x.name,
        pathApi: x.pathApi,
        methodApi: x.methodApi,
      })),
      amount: _data.apiHistorys.length,
    }));
    return new HttpSuccess(data);
  }

  private async buildOrgNameBatch(apiKeys: ApiKey[]): Promise<Map<string, string | null>> {
    const currentRevision = await this.orgRevisionRepository.findOne({
      where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
    });

    if (!currentRevision) {
      return new Map(apiKeys.map((k) => [k.id, null]));
    }

    const currentRevisionId = currentRevision.id;

    const rootIds = [...new Set(apiKeys.map((k) => k.dnaRootId).filter(Boolean))];
    const child1Ids = [...new Set(apiKeys.map((k) => k.dnaChild1Id).filter(Boolean))];
    const child2Ids = [...new Set(apiKeys.map((k) => k.dnaChild2Id).filter(Boolean))];
    const child3Ids = [...new Set(apiKeys.map((k) => k.dnaChild3Id).filter(Boolean))];
    const child4Ids = [...new Set(apiKeys.map((k) => k.dnaChild4Id).filter(Boolean))];

    const [roots, child1s, child2s, child3s, child4s] = await Promise.all([
      rootIds.length > 0
        ? this.orgRootRepository.find({
            where: [
              { id: In(rootIds), orgRevisionId: currentRevisionId },
              { ancestorDNA: In(rootIds), orgRevisionId: currentRevisionId },
            ],
            select: ["id", "ancestorDNA", "orgRootName"],
          })
        : [],
      child1Ids.length > 0
        ? this.orgChild1Repository.find({
            where: [
              { id: In(child1Ids), orgRevisionId: currentRevisionId },
              { ancestorDNA: In(child1Ids), orgRevisionId: currentRevisionId },
            ],
            select: ["id", "ancestorDNA", "orgChild1Name"],
          })
        : [],
      child2Ids.length > 0
        ? this.orgChild2Repository.find({
            where: [
              { id: In(child2Ids), orgRevisionId: currentRevisionId },
              { ancestorDNA: In(child2Ids), orgRevisionId: currentRevisionId },
            ],
            select: ["id", "ancestorDNA", "orgChild2Name"],
          })
        : [],
      child3Ids.length > 0
        ? this.orgChild3Repository.find({
            where: [
              { id: In(child3Ids), orgRevisionId: currentRevisionId },
              { ancestorDNA: In(child3Ids), orgRevisionId: currentRevisionId },
            ],
            select: ["id", "ancestorDNA", "orgChild3Name"],
          })
        : [],
      child4Ids.length > 0
        ? this.orgChild4Repository.find({
            where: [
              { id: In(child4Ids), orgRevisionId: currentRevisionId },
              { ancestorDNA: In(child4Ids), orgRevisionId: currentRevisionId },
            ],
            select: ["id", "ancestorDNA", "orgChild4Name"],
          })
        : [],
    ]);

    const rootMap = new Map(
      roots.map((r) => [r.id, { name: r.orgRootName, ancestorDNA: r.ancestorDNA }]),
    );
    const child1Map = new Map(
      child1s.map((c) => [c.id, { name: c.orgChild1Name, ancestorDNA: c.ancestorDNA }]),
    );
    const child2Map = new Map(
      child2s.map((c) => [c.id, { name: c.orgChild2Name, ancestorDNA: c.ancestorDNA }]),
    );
    const child3Map = new Map(
      child3s.map((c) => [c.id, { name: c.orgChild3Name, ancestorDNA: c.ancestorDNA }]),
    );
    const child4Map = new Map(
      child4s.map((c) => [c.id, { name: c.orgChild4Name, ancestorDNA: c.ancestorDNA }]),
    );

    const result = new Map<string, string | null>();
    for (const apiKey of apiKeys) {
      if (apiKey.accessType === "ALL") {
        result.set(apiKey.id, null);
        continue;
      }

      const parts: string[] = [];

      const getOrgName = (
        dnaId: string,
        orgMap: Map<string, { name: string; ancestorDNA: string }>,
      ): string | null => {
        const byId = orgMap.get(dnaId);
        if (byId) return byId.name;
        for (const [, value] of orgMap) {
          if (value.ancestorDNA === dnaId) return value.name;
        }
        return null;
      };

      if (apiKey.dnaChild4Id) {
        const name = getOrgName(apiKey.dnaChild4Id, child4Map);
        if (name) parts.push(name);
      }
      if (apiKey.dnaChild3Id) {
        const name = getOrgName(apiKey.dnaChild3Id, child3Map);
        if (name) parts.push(name);
      }
      if (apiKey.dnaChild2Id) {
        const name = getOrgName(apiKey.dnaChild2Id, child2Map);
        if (name) parts.push(name);
      }
      if (apiKey.dnaChild1Id) {
        const name = getOrgName(apiKey.dnaChild1Id, child1Map);
        if (name) parts.push(name);
      }
      if (apiKey.dnaRootId) {
        const name = getOrgName(apiKey.dnaRootId, rootMap);
        if (name) parts.push(name);
      }

      result.set(apiKey.id, parts.length > 0 ? parts.join(" ") : null);
    }

    return result;
  }

  /**
   * API รายการ Api Name
   *
   * @summary รายการ Api Name (ADMIN)
   *
   */
  @Get("name")
  async listApiName() {
    const apiName = await this.apiNameRepository.find({
      where: { isActive: true },
      order: { createdAt: "DESC" },
    });
    return new HttpSuccess(apiName);
  }

  /**
   * API สร้าง Api Key
   *
   * @summary สร้าง Api Key (ADMIN)
   *
   */
  @Post("history")
  async getHistory(
    @Body()
    requestBody: {
      startDate: Date;
      endDate: Date;
      apiNameId: string | null;
    },
    @Request() request: RequestWithUser,
  ) {
    if (requestBody.apiNameId) {
      const apiName = await this.apiNameRepository.findOne({
        where: { id: requestBody.apiNameId },
      });
      if (!apiName)
        throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบรายการ Web Service นี้ในระบบ");
    }
    const apiHistory = await AppDataSource.getRepository(ApiHistory)
      .createQueryBuilder("apiHistory")
      .leftJoinAndSelect("apiHistory.apiKey", "apiKey")
      .leftJoinAndSelect("apiHistory.apiName", "apiName")
      .andWhere(requestBody.apiNameId ? `apiHistory.apiNameId = :apiNameId` : "1=1", {
        apiNameId: requestBody.apiNameId,
      })
      .andWhere(
        `apiHistory.createdAt >= DATE(:startDate) AND apiHistory.createdAt <= DATE(:endDate)`,
        {
          startDate: new Date(requestBody.startDate),
          endDate: new Date(
            requestBody.endDate.setDate(new Date(requestBody.endDate).getDate() + 1),
          ),
        },
      )
      .orderBy("apiHistory.createdAt", "DESC")
      .getMany();
    const result = apiHistory.map((x) => ({
      id: x.id,
      apiName: x.apiName.name,
      apiKey: x.apiKey.name,
      createdAt: x.createdAt,
      ipApi: x.ipApi,
    }));

    return new HttpSuccess(result);
  }
}
