import type { Customer } from '../billing/middleware.js';
import type { OAuthCredentials } from '../multitenancy/credential-store.js';
import { createToolAudit } from '../multitenancy/audit-log.js';

const LINKEDIN_API_BASE = 'https://api.linkedin.com/v2';

function getEnvToken(account: string): string {
  const envKey = `LINKEDIN_${account.toUpperCase()}_ACCESS_TOKEN`;
  return process.env[envKey] ?? '';
}

async function resolveToken(args: { account?: string }, customer?: Customer): Promise<string> {
  if (customer) {
    const creds = await customer.getCredential<OAuthCredentials>('linkedin');
    if (!creds) throw new Error('LinkedIn not connected for this account');
    return creds.accessToken;
  }
  const token = getEnvToken(args.account ?? 'default');
  if (!token) throw new Error('Missing LinkedIn credentials. Set LINKEDIN_{ACCOUNT}_ACCESS_TOKEN');
  return token;
}

async function linkedinRequest(
  endpoint: string,
  accessToken: string,
  method: 'GET' | 'POST' = 'GET',
  body?: unknown
) {
  const url = `${LINKEDIN_API_BASE}${endpoint}`;
  const res = await fetch(url, {
    method,
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
      'X-Restli-Protocol-Version': '2.0.0',
    },
    body: body ? JSON.stringify(body) : undefined,
    signal: AbortSignal.timeout(15000),
  });

  if (!res.ok) {
    const error = await res.text();
    throw new Error(`LinkedIn API error (${res.status}): ${error}`);
  }

  return res.json();
}

export async function getProfile(
  args: { account?: string },
  customer?: Customer
): Promise<{
  id: string;
  firstName: string;
  lastName: string;
  email: string;
  picture?: string;
}> {
  const accessToken = await resolveToken(args, customer);

  const res = await fetch(`${LINKEDIN_API_BASE}/userinfo`, {
    headers: { 'Authorization': `Bearer ${accessToken}` },
    signal: AbortSignal.timeout(15000),
  });

  if (!res.ok) {
    const error = await res.text();
    throw new Error(`LinkedIn API error (${res.status}): ${error}`);
  }

  const data = await res.json();
  return {
    id: data.sub,
    firstName: data.given_name ?? '',
    lastName: data.family_name ?? '',
    email: data.email ?? '',
    picture: data.picture ?? '',
  };
}

export async function createPost(
  args: { text: string; visibility?: 'PUBLIC' | 'CONNECTIONS'; account?: string },
  customer?: Customer
): Promise<{ success: boolean; post_id: string; url: string }> {
  const audit = customer ? createToolAudit(customer.id, 'linkedin:createPost') : null;
  const auditArgs = { text: args.text.slice(0, 100) };
  const accessToken = await resolveToken(args, customer);

  const profile = await getProfile(args, customer);
  const authorUrn = `urn:li:person:${profile.id}`;

  const body = {
    author: authorUrn,
    lifecycleState: 'PUBLISHED',
    specificContent: {
      'com.linkedin.ugc.ShareContent': {
        shareCommentary: { text: args.text },
        shareMediaCategory: 'NONE',
      },
    },
    visibility: {
      'com.linkedin.ugc.MemberNetworkVisibility': args.visibility ?? 'PUBLIC',
    },
  };

  try {
    const data = await linkedinRequest('/ugcPosts', accessToken, 'POST', body);
    const postId = data.id ?? '';
    const result = {
      success: true,
      post_id: postId,
      url: postId ? `https://www.linkedin.com/feed/update/${postId}` : '',
    };
    if (audit) await audit.success(auditArgs);
    return result;
  } catch (err) {
    if (audit) await audit.error(auditArgs, String(err));
    throw err;
  }
}

const LINKEDIN_REST_BASE = 'https://api.linkedin.com/rest';

async function linkedinRestRequest(
  endpoint: string,
  accessToken: string,
  method: 'GET' | 'POST' = 'GET',
  body?: unknown
) {
  const url = `${LINKEDIN_REST_BASE}${endpoint}`;
  const res = await fetch(url, {
    method,
    headers: {
      'Authorization': `Bearer ${accessToken}`,
      'Content-Type': 'application/json',
      'LinkedIn-Version': '202603',
      'X-Restli-Protocol-Version': '2.0.0',
    },
    body: body ? JSON.stringify(body) : undefined,
    signal: AbortSignal.timeout(15000),
  });

  if (!res.ok) {
    const error = await res.text();
    throw new Error(`LinkedIn REST API error (${res.status}): ${error}`);
  }

  const text = await res.text();
  return text ? JSON.parse(text) : {};
}

async function uploadVideo(videoUrl: string, ownerUrn: string, accessToken: string): Promise<string> {
  // Step 1: Download the video
  const downloadRes = await fetch(videoUrl, { signal: AbortSignal.timeout(120000) });
  if (!downloadRes.ok) throw new Error(`Failed to download video from ${videoUrl}: ${downloadRes.status}`);
  const buffer = await downloadRes.arrayBuffer();
  const fileSizeBytes = buffer.byteLength;

  // Step 2: Initialize upload
  const initData = await linkedinRestRequest('/videos?action=initializeUpload', accessToken, 'POST', {
    initializeUploadRequest: {
      owner: ownerUrn,
      fileSizeBytes,
      uploadCaptions: false,
      uploadThumbnail: false,
    },
  });
  const { uploadInstructions, video: videoUrn, uploadToken } = initData.value;

  // Step 3: Upload chunks and collect ETags
  const CHUNK_SIZE = 4_194_304;
  const etags: string[] = [];
  for (const instruction of uploadInstructions) {
    const { firstByte, lastByte, uploadUrl } = instruction;
    const chunk = buffer.slice(firstByte, lastByte + 1);
    const putRes = await fetch(uploadUrl, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/octet-stream' },
      body: chunk,
      signal: AbortSignal.timeout(60000),
    });
    if (!putRes.ok) throw new Error(`Chunk upload failed (${putRes.status})`);
    const etag = putRes.headers.get('ETag') ?? putRes.headers.get('etag') ?? '';
    etags.push(etag.replace(/^"|"$/g, ''));
  }
  void CHUNK_SIZE; // referenced via uploadInstructions slice ranges

  // Step 4: Finalize upload
  await linkedinRestRequest('/videos?action=finalizeUpload', accessToken, 'POST', {
    finalizeUploadRequest: {
      video: videoUrn,
      uploadToken,
      uploadedPartIds: etags,
    },
  });

  // Step 5: Poll until AVAILABLE
  const deadline = Date.now() + 90_000;
  while (Date.now() < deadline) {
    await new Promise(r => setTimeout(r, 3000));
    const status = await linkedinRestRequest(`/videos/${encodeURIComponent(videoUrn)}`, accessToken);
    if (status.status === 'AVAILABLE') return videoUrn;
    if (status.status === 'FAILED') throw new Error(`LinkedIn video processing failed: ${JSON.stringify(status)}`);
  }
  throw new Error('LinkedIn video processing timed out after 90 seconds');
}

export async function createVideoPost(
  args: { video_url: string; text: string; visibility?: 'PUBLIC' | 'CONNECTIONS'; account?: string },
  customer?: Customer
): Promise<{ success: boolean; post_id: string; url: string }> {
  const audit = customer ? createToolAudit(customer.id, 'linkedin:createVideoPost') : null;
  const auditArgs = { text: args.text.slice(0, 100), video_url: args.video_url };
  const accessToken = await resolveToken(args, customer);

  const profile = await getProfile(args, customer);
  const authorUrn = `urn:li:person:${profile.id}`;

  try {
    const videoUrn = await uploadVideo(args.video_url, authorUrn, accessToken);

    const postRes = await fetch(`${LINKEDIN_REST_BASE}/posts`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
        'LinkedIn-Version': '202603',
        'X-Restli-Protocol-Version': '2.0.0',
      },
      body: JSON.stringify({
        author: authorUrn,
        commentary: args.text,
        visibility: args.visibility ?? 'PUBLIC',
        distribution: {
          feedDistribution: 'MAIN_FEED',
          targetEntities: [],
          thirdPartyDistributionChannels: [],
        },
        content: { media: { id: videoUrn } },
        lifecycleState: 'PUBLISHED',
        isReshareDisabledByAuthor: false,
      }),
      signal: AbortSignal.timeout(15000),
    });

    if (postRes.status !== 201) {
      const error = await postRes.text();
      throw new Error(`LinkedIn post creation failed (${postRes.status}): ${error}`);
    }

    const post_id = postRes.headers.get('x-restli-id') ?? '';
    const result = {
      success: true,
      post_id,
      url: post_id ? `https://www.linkedin.com/feed/update/${post_id}` : '',
    };
    if (audit) await audit.success(auditArgs);
    return result;
  } catch (err) {
    if (audit) await audit.error(auditArgs, String(err));
    throw err;
  }
}

export async function searchConnections(
  args: { keywords?: string; account?: string },
  _customer?: Customer
): Promise<{ message: string }> {
  throw new Error(
    'LinkedIn connections search requires the LinkedIn Partnership Program. ' +
    'Public API access to connections was removed. ' +
    'Apply at https://developer.linkedin.com/partner-programs'
  );
}

export async function sendMessage(
  args: { recipient_id: string; message: string; account?: string },
  _customer?: Customer
): Promise<{ message: string }> {
  throw new Error(
    'LinkedIn messaging requires the LinkedIn Partnership Program. ' +
    'Direct messaging is not available through the public API. ' +
    'Apply at https://developer.linkedin.com/partner-programs'
  );
}
