import { DatabaseConnector, ConnectionConfig, QueryResult } from '../interfaces/DatabaseConnector';
import { ConnectionTestResult, TableInfo } from '../types/externalDbTypes';
// @ts-ignore
import * as mssql from 'mssql';

export class MSSQLConnector implements DatabaseConnector {
  private pool: mssql.ConnectionPool | null = null;
  private config: ConnectionConfig;

  constructor(config: ConnectionConfig) {
    this.config = config;
  }

  async connect(): Promise<void> {
    if (!this.pool) {
      const config: mssql.config = {
        server: this.config.host,
        port: this.config.port,
        user: this.config.user,
        password: this.config.password,
        database: this.config.database,
        options: {
          encrypt: this.config.ssl === true,
          trustServerCertificate: true
        },
        connectionTimeout: this.config.connectionTimeoutMillis || 15000,
        requestTimeout: this.config.queryTimeoutMillis || 15000
      };
      this.pool = await new mssql.ConnectionPool(config).connect();
    }
  }

  async disconnect(): Promise<void> {
    if (this.pool) {
      await this.pool.close();
      this.pool = null;
    }
  }

  async testConnection(): Promise<ConnectionTestResult> {
    const startTime = Date.now();
    try {
      await this.connect();
      
      // 버전 정보 조회
      const versionResult = await this.pool!.request().query('SELECT @@VERSION as version');
      
      // 데이터베이스 크기 조회
      const sizeResult = await this.pool!.request()
        .input('dbName', mssql.VarChar, this.config.database)
        .query(`
          SELECT SUM(size * 8 * 1024) as size
          FROM sys.master_files
          WHERE database_id = DB_ID(@dbName)
        `);
      
      const responseTime = Date.now() - startTime;
      
      return {
        success: true,
        message: "MSSQL 연결이 성공했습니다.",
        details: {
          response_time: responseTime,
          server_version: versionResult.recordset[0]?.version || "알 수 없음",
          database_size: this.formatBytes(parseInt(sizeResult.recordset[0]?.size || "0")),
        },
      };
    } catch (error: any) {
      return {
        success: false,
        message: "MSSQL 연결에 실패했습니다.",
        error: {
          code: "CONNECTION_FAILED",
          details: error.message || "알 수 없는 오류",
        },
      };
    }
  }

  async executeQuery(query: string, params: any[] = []): Promise<QueryResult> {
    try {
      await this.connect();
      const request = this.pool!.request();
      
      // 파라미터 바인딩 (SQL Server는 @param1, @param2 형식이지만 여기서는 ? 사용)
      params.forEach((param, index) => {
        request.input(`param${index + 1}`, param);
      });
      
      const result = await request.query(query);
      return {
        rows: result.recordset,
        rowCount: result.rowsAffected[0],
      };
    } catch (error: any) {
      throw new Error(`쿼리 실행 오류: ${error.message}`);
    }
  }

  async getTables(): Promise<TableInfo[]> {
    try {
      await this.connect();
      const result = await this.pool!.request().query(`
        SELECT 
          t.TABLE_NAME as table_name,
          c.COLUMN_NAME as column_name,
          c.DATA_TYPE as data_type,
          c.IS_NULLABLE as is_nullable,
          c.COLUMN_DEFAULT as column_default,
          CAST(p.value AS NVARCHAR(MAX)) as description
        FROM INFORMATION_SCHEMA.TABLES t
        LEFT JOIN INFORMATION_SCHEMA.COLUMNS c 
          ON c.TABLE_NAME = t.TABLE_NAME
        LEFT JOIN sys.extended_properties p
          ON p.major_id = OBJECT_ID(t.TABLE_NAME)
          AND p.minor_id = 0
          AND p.name = 'MS_Description'
        WHERE t.TABLE_TYPE = 'BASE TABLE'
        ORDER BY t.TABLE_NAME, c.ORDINAL_POSITION
      `);

      // 결과를 TableInfo[] 형식으로 변환
      const tables = new Map<string, TableInfo>();
      
      result.recordset.forEach((row: any) => {
        if (!tables.has(row.table_name)) {
          tables.set(row.table_name, {
            table_name: row.table_name,
            columns: [],
            description: row.description || null
          });
        }
        
        if (row.column_name) {
          tables.get(row.table_name)!.columns.push({
            column_name: row.column_name,
            data_type: row.data_type,
            is_nullable: row.is_nullable === 'YES' ? 'Y' : 'N',
            column_default: row.column_default
          });
        }
      });

      return Array.from(tables.values());
    } catch (error: any) {
      throw new Error(`테이블 목록 조회 오류: ${error.message}`);
    }
  }

  async getColumns(tableName: string): Promise<any[]> {
    try {
      await this.connect();
      const result = await this.pool!.request()
        .input('tableName', mssql.VarChar, tableName)
        .query(`
          SELECT 
            c.COLUMN_NAME as name,
            c.DATA_TYPE as type,
            c.IS_NULLABLE as nullable,
            c.COLUMN_DEFAULT as default_value,
            c.CHARACTER_MAXIMUM_LENGTH as max_length,
            c.NUMERIC_PRECISION as precision,
            c.NUMERIC_SCALE as scale,
            CAST(p.value AS NVARCHAR(MAX)) as description
          FROM INFORMATION_SCHEMA.COLUMNS c
          LEFT JOIN sys.columns sc
            ON sc.object_id = OBJECT_ID(@tableName)
            AND sc.name = c.COLUMN_NAME
          LEFT JOIN sys.extended_properties p
            ON p.major_id = sc.object_id
            AND p.minor_id = sc.column_id
            AND p.name = 'MS_Description'
          WHERE c.TABLE_NAME = @tableName
          ORDER BY c.ORDINAL_POSITION
        `);
      
      return result.recordset;
    } catch (error: any) {
      throw new Error(`컬럼 정보 조회 오류: ${error.message}`);
    }
  }

  private formatBytes(bytes: number): string {
    if (bytes === 0) return "0 Bytes";
    const k = 1024;
    const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
  }
}