import https from "https"
import http from "http"
import querystring from "querystring"
import { resolveAgreementConfig, KV_DEVECO_CODE_PRIVACY_ACCEPTED, type AgreementConfig } from "@/cli/deveco-legal"
import { devecoAuth } from "@/plugin/deveco"
import * as Log from "@opencode-ai/core/util/log"

const log = Log.create({ service: "deveco-agreement" })

// ============ Data Models ============

export enum AgreementStatus {
  COMPLIANT = "compliant",
  NEED_SIGN = "need_sign",
  NEED_RE_SIGN = "need_re_sign",
  NETWORK_ERROR = "network_error",
}

export interface SignInfo {
  isAgree: boolean
  version: number
  agrType: number
  country: string
  language: string
  newestVersion: number
  newestSubVersion: number
  needSign: boolean
  matchedVersion: number
  matchedSubVersion: number
  subVersion: number
  signType: number
  branchId: number
  signTime: number
  cg: string
  contentTag: string
  latestVersion: number
  rpt: number
}

export interface VersionInfo {
  newestVersion: number
  agrType: number
  country: string
  branchId: number
  cg: string
  matchedVersion: number
  matchedSubVersion: number
  newestSubVersion: number
  latestVersion: number
}

export interface AgreementQueryResult {
  status: AgreementStatus
  signInfo: SignInfo | null
  versionInfo: VersionInfo | null
  error?: string
}

export interface AgreementSignResult {
  success: boolean
  isUpload: boolean
  error?: string
  refreshedToken?: boolean
}

export interface AgreementCheckResult {
  privacyStatus: AgreementStatus
  termsStatus: AgreementStatus
  overallStatus: AgreementStatus
  canEnter: boolean
  hasLocalCache: boolean
}

// ============ TMS HTTP Request ============

// Actual error values returned by the TMS API (lowercase with spaces)
const SESSION_TIMEOUT_ERROR = "session timeout"
const INVALID_SESSION_ERROR = "invalid session"

const KEY_ERROR = "error"
const ERROR_CODE = "errorCode"

interface TmsFormBody {
  nsp_svc: string
  access_token: string
  request: string
}

/**
 * Send a POST request to the TMS API using application/x-www-form-urlencoded encoding (NSP protocol format).
 * The `request` field is an embedded JSON string inside the form-encoded body.
 */
async function tmsPost(tmsUrl: string, body: TmsFormBody): Promise<string> {
  const parsedUrl = new URL(tmsUrl)
  const isHttps = parsedUrl.protocol === "https:"
  const httpModule = isHttps ? https : http

  // Encode as application/x-www-form-urlencoded (NSP protocol format)
  const formBody = querystring.stringify({
    nsp_svc: body.nsp_svc,
    access_token: body.access_token,
    request: body.request,
  })

  const headers: Record<string, string> = {
    "Content-Type": "application/x-www-form-urlencoded",
    "Content-Length": String(Buffer.byteLength(formBody)),
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
    "accept-language": "zh-CN",
  }

  return new Promise((resolve, reject) => {
    const options: http.RequestOptions | https.RequestOptions = {
      method: "POST",
      headers,
      timeout: 20000,
    }

    const req = httpModule.request(tmsUrl, options, (res) => {
      let data = ""
      res.on("data", (chunk: Buffer | string) => {
        data += chunk
      })
      res.on("end", () => {
        resolve(data)
      })
    })

    req.on("error", (err: Error) => {
      reject(err)
    })
    req.on("timeout", () => {
      req.destroy()
      log.error("TMS request timeout", { url: tmsUrl })
      reject(new Error("TMS request timeout"))
    })

    req.write(formBody)
    req.end()
  })
}

// ============ Session Timeout Retry ============

function isSessionTimeoutError(errorValue: unknown): boolean {
  return errorValue === SESSION_TIMEOUT_ERROR || errorValue === INVALID_SESSION_ERROR
}

async function handleSessionTimeoutAndRetry<T>(
  rawResponse: string,
  retryApiCall: (accessToken: string) => Promise<string>,
  accessToken: string,
  parseResponse: (raw: string) => T,
): Promise<{ result: T; refreshedToken: boolean }> {
  let resJson: Record<string, unknown>
  try {
    resJson = JSON.parse(rawResponse) as Record<string, unknown>
  } catch {
    log.warn("failed to parse TMS response as JSON, falling through to raw parse")
    return { result: parseResponse(rawResponse), refreshedToken: false }
  }

  if (isSessionTimeoutError(resJson[KEY_ERROR])) {
    log.info("session timeout detected, refreshing token")
    const newTokens = await devecoAuth.refreshToken()
    if (!newTokens?.accessToken) {
      log.warn("token refresh failed, cannot retry")
      return { result: parseResponse(rawResponse), refreshedToken: false }
    }

    const retryRaw = await retryApiCall(newTokens.accessToken)
    return { result: parseResponse(retryRaw), refreshedToken: true }
  }

  return { result: parseResponse(rawResponse), refreshedToken: false }
}

// ============ AgreementService ============

function parseSignInfo(raw: Record<string, unknown>): SignInfo {
  return {
    isAgree: (raw.isAgree as boolean) ?? false,
    version: (raw.version as number) ?? 0,
    agrType: (raw.agrType as number) ?? 0,
    country: (raw.country as string) ?? "",
    language: (raw.language as string) ?? "",
    newestVersion: (raw.newestVersion as number) ?? 0,
    newestSubVersion: (raw.newestSubVersion as number) ?? 0,
    needSign: (raw.needSign as boolean) ?? false,
    matchedVersion: (raw.matchedVersion as number) ?? 0,
    matchedSubVersion: (raw.matchedSubVersion as number) ?? 0,
    subVersion: (raw.subVersion as number) ?? 0,
    signType: (raw.signType as number) ?? 0,
    branchId: (raw.branchId as number) ?? 0,
    signTime: (raw.signTime as number) ?? 0,
    cg: (raw.cg as string) ?? "",
    contentTag: (raw.contentTag as string) ?? "",
    latestVersion: (raw.latestVersion as number) ?? 0,
    rpt: (raw.rpt as number) ?? 0,
  }
}

function parseVersionInfo(raw: Record<string, unknown>): VersionInfo {
  return {
    newestVersion: (raw.newestVersion as number) ?? 0,
    agrType: (raw.agrType as number) ?? 0,
    country: (raw.country as string) ?? "",
    branchId: (raw.branchId as number) ?? 0,
    cg: (raw.cg as string) ?? "",
    matchedVersion: (raw.matchedVersion as number) ?? 0,
    matchedSubVersion: (raw.matchedSubVersion as number) ?? 0,
    newestSubVersion: (raw.newestSubVersion as number) ?? 0,
    latestVersion: (raw.latestVersion as number) ?? 0,
  }
}

class AgreementService {
  private config = resolveAgreementConfig()

  /** Update agreement config from project-level settings. Called before any API use. */
  configure(overrides?: AgreementConfig) {
    this.config = resolveAgreementConfig(overrides)
  }

  /**
   * 查询所有协议签署状态(一次请求传入 PRIVACY_ID 和 TERMS_ID)
   */
  async queryAgreement(accessToken: string): Promise<AgreementQueryResult> {
    // agrInfo includes both agreement IDs in one request
    const requestJson: string = JSON.stringify({
      obtainVersion: true,
      agrInfo: [
        {
          agrType: this.config.privacy_id,
          country: "CN",
          signType: 0,
          branchId: 0,
        },
        {
          agrType: this.config.terms_id,
          country: "CN",
          signType: 0,
          branchId: 0,
        },
      ],
    })

    const tmsBody: TmsFormBody = {
      nsp_svc: "as.user.query",
      access_token: accessToken,
      request: requestJson,
    }

    try {
      const rawResponse = await tmsPost(this.config.tms_url, tmsBody)

      const { result } = await handleSessionTimeoutAndRetry(
        rawResponse,
        async (newToken: string) => {
          const retryBody: TmsFormBody = {
            nsp_svc: "as.user.query",
            access_token: newToken,
            request: requestJson,
          }
          return tmsPost(this.config.tms_url, retryBody)
        },
        accessToken,
        (raw: string) => this.parseQueryResponse(raw),
      )

      return result
    } catch (err) {
      log.error(`query agreement exception: ${err instanceof Error ? err.message : String(err)}`)
      return {
        status: AgreementStatus.NETWORK_ERROR,
        signInfo: null,
        versionInfo: null,
        error: err instanceof Error ? err.message : "Network error",
      }
    }
  }

  private parseQueryResponse(raw: string): AgreementQueryResult {
    try {
      const resJson = JSON.parse(raw) as Record<string, unknown>

      if (isSessionTimeoutError(resJson[KEY_ERROR])) {
        log.error("query agreement response: session timeout", { error: resJson[KEY_ERROR] })
        return {
          status: AgreementStatus.NEED_SIGN,
          signInfo: null,
          versionInfo: null,
          error: resJson[KEY_ERROR] as string,
        }
      }

      const errorCode = resJson[ERROR_CODE] as number | undefined
      if (errorCode === 0) {
        const signArr = resJson.signInfo as Array<Record<string, unknown>> | undefined
        if (!signArr || signArr.length === 0) {
          log.warn("query agreement response: empty signInfo array")
          return {
            status: AgreementStatus.NEED_SIGN,
            signInfo: null,
            versionInfo: null,
          }
        }

        // Check each agreement: any needSign=true or isAgree=false → NEED_SIGN/NEED_RE_SIGN
        let allCompliant = true
        let anyNeedReSign = false

        for (const signEntry of signArr) {
          const parsedSign = parseSignInfo(signEntry)
          if (parsedSign.needSign) {
            allCompliant = false
            // If needSign but isAgree=true → version expired → NEED_RE_SIGN
            // If needSign and isAgree=false → never signed → NEED_SIGN
            if (parsedSign.isAgree && !compareAgreementVersion(parsedSign.newestVersion, parsedSign.version)) {
              anyNeedReSign = true
            }
          }
          if (!parsedSign.isAgree) {
            allCompliant = false
          }
          if (parsedSign.isAgree && !compareAgreementVersion(parsedSign.newestVersion, parsedSign.version)) {
            allCompliant = false
            anyNeedReSign = true
          }
        }

        if (allCompliant) {
          return {
            status: AgreementStatus.COMPLIANT,
            signInfo: parseSignInfo(signArr[0]),
            versionInfo: null,
          }
        }

        return {
          status: anyNeedReSign ? AgreementStatus.NEED_RE_SIGN : AgreementStatus.NEED_SIGN,
          signInfo: parseSignInfo(signArr[0]),
          versionInfo: null,
        }
      }

      return {
        status: AgreementStatus.NEED_SIGN,
        signInfo: null,
        versionInfo: null,
        error: `errorCode=${errorCode}`,
      }
    } catch (err) {
      log.error("failed to parse agreement query response", { error: err instanceof Error ? err.message : String(err) })
      return {
        status: AgreementStatus.NEED_SIGN,
        signInfo: null,
        versionInfo: null,
        error: "Failed to parse query response",
      }
    }
  }

  /**
   * 签署两个协议(隐私+用户协议),一次请求同时签署
   */
  async signAgreement(accessToken: string, isRetry: boolean): Promise<AgreementSignResult> {
    const requestJson: string = JSON.stringify({
      signInfo: [
        {
          agrType: this.config.privacy_id,
          country: "CN",
          language: "zh_CN",
          isAgree: true,
        },
        {
          agrType: this.config.terms_id,
          country: "CN",
          language: "zh_CN",
          isAgree: true,
        },
      ],
    })

    const tmsBody: TmsFormBody = {
      nsp_svc: "as.user.sign",
      access_token: accessToken,
      request: requestJson,
    }

    try {
      const rawResponse = await tmsPost(this.config.tms_url, tmsBody)

      const { result, refreshedToken } = await handleSessionTimeoutAndRetry(
        rawResponse,
        async (newToken: string) => {
          const retryBody: TmsFormBody = {
            nsp_svc: "as.user.sign",
            access_token: newToken,
            request: requestJson,
          }
          return tmsPost(this.config.tms_url, retryBody)
        },
        accessToken,
        (raw: string) => this.parseSignResponse(raw),
      )

      return { ...result, refreshedToken }
    } catch (err) {
      log.error(`sign agreement exception: ${err instanceof Error ? err.message : String(err)}`)
      return {
        success: false,
        isUpload: false,
        error: err instanceof Error ? err.message : "Network error",
      }
    }
  }

  private parseSignResponse(raw: string): AgreementSignResult {
    try {
      const resJson = JSON.parse(raw) as Record<string, unknown>

      if (isSessionTimeoutError(resJson[KEY_ERROR])) {
        log.error("sign agreement response: session timeout", { error: resJson[KEY_ERROR] })
        return {
          success: false,
          isUpload: false,
          error: resJson[KEY_ERROR] as string,
        }
      }

      const errorCode = resJson[ERROR_CODE] as number | undefined
      if (errorCode === 0) {
        return { success: true, isUpload: true }
      }

      log.error("sign agreement response: non-zero errorCode", { errorCode })
      return {
        success: false,
        isUpload: false,
        error: `errorCode=${errorCode}`,
      }
    } catch (err) {
      log.error("failed to parse agreement sign response", { error: err instanceof Error ? err.message : String(err) })
      return {
        success: false,
        isUpload: false,
        error: "Failed to parse sign response",
      }
    }
  }

  /**
   * 综合检查两个协议的签署状态(一次请求查询所有协议)
   */
  async checkAllAgreements(
    accessToken: string,
    kvStore: { get: (key: string, defaultValue: unknown) => unknown },
  ): Promise<AgreementCheckResult> {
    const queryResult = await this.queryAgreement(accessToken)

    const overallStatus = queryResult.status
    const hasLocalCache = kvStore.get(KV_DEVECO_CODE_PRIVACY_ACCEPTED, false) === true

    // For privacy/terms individual status, both share the same overall query result
    // since we query both in one request
    const privacyStatus = overallStatus
    const termsStatus = overallStatus

    // COMPLIANT → can enter
    if (overallStatus === AgreementStatus.COMPLIANT) {
      return {
        privacyStatus,
        termsStatus,
        overallStatus,
        canEnter: true,
        hasLocalCache,
      }
    }

    // NETWORK_ERROR → show privacy step (let user see network error / retry)
    // Even with local cache, don't silently bypass — user should explicitly see the status
    if (overallStatus === AgreementStatus.NETWORK_ERROR) {
      return {
        privacyStatus,
        termsStatus,
        overallStatus,
        canEnter: false,
        hasLocalCache,
      }
    }

    // NEED_SIGN / NEED_RE_SIGN → cannot enter
    return {
      privacyStatus,
      termsStatus,
      overallStatus,
      canEnter: false,
      hasLocalCache,
    }
  }
}

// ============ Version Comparison ============

function compareAgreementVersion(newestVersion: number, signedVersion: number): boolean {
  return signedVersion >= newestVersion
}

// ============ Export ============

export const agreementService = new AgreementService()
export { compareAgreementVersion }